/* * Copyright (c) by Jaroslav Kysela * Routines for control of SoundBlaster mixer * TODO: Routines for CTL1335 mixer (Sound Blaster 2.0 CD). * If you have this soundcard, mail me!!! * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #define __SND_OSS_COMPAT__ #include "driver.h" #include "pcm.h" #include "mixer.h" #include "sb.h" void snd_sbmixer_write( sbmixer_t *mixer, unsigned char reg, unsigned char data ) { outb( reg, SBP1( mixer -> port, MIXER_ADDR ) ); snd_delay( 1 ); outb( data, SBP1( mixer -> port, MIXER_DATA ) ); snd_delay( 1 ); } unsigned char snd_sbmixer_read( sbmixer_t *mixer, unsigned char reg ) { unsigned char result; outb( reg, SBP1( mixer -> port, MIXER_ADDR ) ); snd_delay( 1 ); result = inb( SBP1( mixer -> port, MIXER_DATA ) ); snd_delay( 1 ); return result; } /* * private_value * 0x000000ff - register * 0x00000f00 - left shift * 0x0000f000 - right shift */ static void snd_sbmixer_record_source( snd_kmixer_t *mixer, snd_kmixer_channel_t *channel, int enable ) { unsigned long flags; unsigned char mixs = SB_DSP_MIXS_NONE, mask; sbmixer_t *sbmix; sbmix = (sbmixer_t *)mixer -> private_data; switch ( channel -> hw.priority ) { case SND_MIXER_PRI_MIC: mask = 1; break; case SND_MIXER_PRI_CD: mask = 2; break; case SND_MIXER_PRI_LINE: mask = 4; break; default: mask = 0; break; /* master */ } if ( enable ) mixer -> private_value |= mask; else mixer -> private_value &= ~mask; if ( mixer -> private_value == 0 ) mixs = SB_DSP_MIXS_NONE; else if ( mixer -> private_value & 1 ) mixs = SB_DSP_MIXS_MIC; else if ( mixer -> private_value & 2 ) mixs = SB_DSP_MIXS_CD; else mixs = SB_DSP_MIXS_LINE; snd_spin_lock( sbmix, mixer, &flags ); mixs |= snd_sbmixer_read( sbmix, SB_DSP_RECORD_SOURCE ) & ~6; snd_sbmixer_write( sbmix, SB_DSP_RECORD_SOURCE, mixs ); snd_spin_unlock( sbmix, mixer, &flags ); } static void snd_sbmixer_volume_level( snd_kmixer_t *mixer, snd_kmixer_channel_t *channel, int left, int right ) { unsigned long flags; unsigned char reg, left_shift, right_shift, data, mask; sbmixer_t *sbmix; sbmix = (sbmixer_t *)mixer -> private_data; reg = (unsigned char)channel -> hw.private_value; left_shift = (unsigned char)((channel -> hw.private_value >> 8) & 0x0f); right_shift = (unsigned char)((channel -> hw.private_value >> 12) & 0x0f); #if 0 snd_printk( "volume: reg = 0x%x, left_shift = 0x%x, right_shift = 0x%x\n", reg, left_shift, right_shift ); #endif left <<= left_shift; mask = channel -> hw.max << left_shift; if ( left_shift != right_shift ) { left |= right << right_shift; mask |= channel -> hw.max << right_shift; } snd_spin_lock( sbmix, mixer, &flags ); data = snd_sbmixer_read( sbmix, reg ) & ~mask; snd_sbmixer_write( sbmix, reg, left | data ); snd_spin_unlock( sbmix, mixer, &flags ); } #define SB_DSP_MIXS (sizeof(snd_sbmixer_pro_mixs)/sizeof(struct snd_stru_mixer_channel_hw)) #define SB_DSP_PRIVATE( reg, left_shift, right_shift ) (reg|(left_shift<<8)|(right_shift<<12)) static struct snd_stru_mixer_channel_hw snd_sbmixer_pro_mixs[] = { { SND_MIXER_PRI_MASTER, /* priority */ SND_MIXER_PRI_PARENT, /* parent priority */ SND_MIXER_ID_MASTER, /* device name */ SND_MIXER_OSS_VOLUME, /* OSS device # */ 0, 1, 0, 0, 0, /* mute/stereo/record/digital/input */ 0, 7, /* max value */ -4600, 0, 575, /* min, max, step - dB */ SB_DSP_PRIVATE( SB_DSP_MASTER_DEV, 5, 1 ), NULL, /* compute dB -> linear */ NULL, /* compute linear -> dB */ NULL, /* record source */ NULL, /* set mute */ snd_sbmixer_volume_level, /* set volume level */ }, { SND_MIXER_PRI_PCM, /* priority */ SND_MIXER_PRI_MASTER, /* parent priority */ SND_MIXER_ID_PCM, /* device name */ SND_MIXER_OSS_PCM, /* OSS device # */ 0, 1, 0, 0, 0, /* mute/stereo/record/digital/input */ 0, 7, /* max value */ -4600, 0, 575, /* min, max, step - dB */ SB_DSP_PRIVATE( SB_DSP_PCM_DEV, 5, 1 ), NULL, /* compute dB -> linear */ NULL, /* compute linear -> dB */ NULL, /* record source */ NULL, /* set mute */ snd_sbmixer_volume_level, /* set volume level */ }, { SND_MIXER_PRI_LINE, /* priority */ SND_MIXER_PRI_MASTER, /* parent priority */ SND_MIXER_ID_LINE, /* device name */ SND_MIXER_OSS_LINE, /* OSS device # */ 0, 1, 1, 0, 1, /* mute/stereo/record/digital/input */ 0, 7, /* max value */ -4600, 0, 575, /* min, max, step - dB */ SB_DSP_PRIVATE( SB_DSP_LINE_DEV, 5, 1 ), NULL, /* compute dB -> linear */ NULL, /* compute linear -> dB */ snd_sbmixer_record_source, /* record source */ NULL, /* set mute */ snd_sbmixer_volume_level, /* set volume level */ }, { SND_MIXER_PRI_CD, /* priority */ SND_MIXER_PRI_MASTER, /* parent priority */ SND_MIXER_ID_CD, /* device name */ SND_MIXER_OSS_CD, /* OSS device # */ 0, 1, 1, 0, 1, /* mute/stereo/record/digital/input */ 0, 7, /* max value */ -4600, 0, 575, /* min, max, step - dB */ SB_DSP_PRIVATE( SB_DSP_CD_DEV, 5, 1 ), NULL, /* compute dB -> linear */ NULL, /* compute linear -> dB */ snd_sbmixer_record_source, /* record source */ NULL, /* set mute */ snd_sbmixer_volume_level, /* set volume level */ }, { SND_MIXER_PRI_FM, /* priority */ SND_MIXER_PRI_MASTER, /* parent priority */ SND_MIXER_ID_FM, /* device name */ SND_MIXER_OSS_SYNTH, /* OSS device # */ 0, 1, 0, 0, 0, /* mute/stereo/record/digital/input */ 0, 7, /* max value */ -4600, 0, 575, /* min, max, step - dB */ SB_DSP_PRIVATE( SB_DSP_CD_DEV, 5, 1 ), NULL, /* compute dB -> linear */ NULL, /* compute linear -> dB */ snd_sbmixer_record_source, /* record source */ NULL, /* set mute */ snd_sbmixer_volume_level, /* set volume level */ }, { SND_MIXER_PRI_MIC, /* priority */ SND_MIXER_PRI_MASTER, /* parent priority */ SND_MIXER_ID_MIC, /* device name */ SND_MIXER_OSS_MIC, /* OSS device # */ 0, 0, 1, 0, 0, /* mute/stereo/record/digital/input */ 0, 3, /* max value */ -4600, 0, 1150, /* min, max, step - dB */ SB_DSP_PRIVATE( SB_DSP_MIC_DEV, 1, 1 ), NULL, /* compute dB -> linear */ NULL, /* compute linear -> dB */ snd_sbmixer_record_source, /* record source */ NULL, /* set mute */ snd_sbmixer_volume_level, /* set volume level */ } }; /* * private_value * 0x000000ff - register * 0x00000f00 - left shift * 0x0000f000 - right shift */ static void snd_sb16mixer_record_source( snd_kmixer_t *mixer, snd_kmixer_channel_t *channel, int enable ) { unsigned long flags; unsigned int mask; sbmixer_t *sbmix; sbmix = (sbmixer_t *)mixer -> private_data; switch ( channel -> hw.priority ) { case SND_MIXER_PRI_MIC: mask = 0x01; break; case SND_MIXER_PRI_CD: mask = 0x04; break; case SND_MIXER_PRI_LINE: mask = 0x10; break; case SND_MIXER_PRI_SYNTHESIZER: mask = 0x40; break; default: mask = 0; break; /* master */ } if ( enable ) mixer -> private_value |= mask; else mixer -> private_value &= ~mask; snd_spin_lock( sbmix, mixer, &flags ); snd_sbmixer_write( sbmix, SB_DSP4_INPUT_LEFT, ((unsigned char)mixer -> private_value) & 0xff ); mask = ((unsigned char)mixer -> private_value >> 1) & 0xfe; mask |= (unsigned char)mixer -> private_value & 0x01; snd_sbmixer_write( sbmix, SB_DSP4_INPUT_RIGHT, mask ); snd_spin_unlock( sbmix, mixer, &flags ); } static void snd_sb16mixer_mute( snd_kmixer_t *mixer, snd_kmixer_channel_t *channel, unsigned int mute ) { unsigned long flags; unsigned int lmask, rmask; sbmixer_t *sbmix; sbmix = (sbmixer_t *)mixer -> private_data; switch ( channel -> hw.priority ) { case SND_MIXER_PRI_MIC: lmask = rmask = 0x0100; break; case SND_MIXER_PRI_CD: lmask = 0x0400; rmask = 0x0200; break; case SND_MIXER_PRI_LINE: lmask = 0x1000; rmask = 0x0800; break; default: lmask = rmask = 0; } if ( mute & SND_MIX_MUTE_LEFT ) mixer -> private_value &= ~lmask; else mixer -> private_value |= lmask; if ( mute & SND_MIX_MUTE_RIGHT ) mixer -> private_value &= ~rmask; else mixer -> private_value |= rmask; #if 0 snd_printk( "private_value = 0x%x\n", mixer -> private_value ); #endif snd_spin_lock( sbmix, mixer, &flags ); snd_sbmixer_write( sbmix, 0x3c, (mixer -> private_value >> 8) & 0xff ); snd_spin_unlock( sbmix, mixer, &flags ); } static void snd_sb16mixer_volume_level( snd_kmixer_t *mixer, snd_kmixer_channel_t *channel, int left, int right ) { unsigned long flags; unsigned char reg, left_shift, right_shift, data, mask; sbmixer_t *sbmix; sbmix = (sbmixer_t *)mixer -> private_data; reg = (unsigned char)channel -> hw.private_value; left_shift = (unsigned char)((channel -> hw.private_value >> 8) & 0x0f); right_shift = (unsigned char)((channel -> hw.private_value >> 12) & 0x0f); #if 0 snd_printk( "volume: reg = 0x%x, left_shift = 0x%x, right_shift = 0x%x\n", reg, left_shift, right_shift ); #endif left <<= left_shift; mask = channel -> hw.max << left_shift; right <<= right_shift; snd_spin_lock( sbmix, mixer, &flags ); data = snd_sbmixer_read( sbmix, reg ) & ~mask; snd_sbmixer_write( sbmix, reg, left | data ); if ( channel -> hw.stereo ) { data = snd_sbmixer_read( sbmix, reg + 1 ) & ~mask; snd_sbmixer_write( sbmix, reg + 1, right | data ); } snd_spin_unlock( sbmix, mixer, &flags ); } #define SB_DSP4_MIXS (sizeof(snd_sbmixer_16_mixs)/sizeof(struct snd_stru_mixer_channel_hw)) #define SB_DSP4_PRIVATE( reg, left_shift, right_shift ) (reg|(left_shift<<8)|(right_shift<<12)) static struct snd_stru_mixer_channel_hw snd_sbmixer_16_mixs[] = { { SND_MIXER_PRI_MASTER, /* priority */ SND_MIXER_PRI_PARENT, /* parent priority */ SND_MIXER_ID_MASTER, /* device name */ SND_MIXER_OSS_VOLUME, /* OSS device # */ 0, 1, 0, 0, 0, /* mute/stereo/record/digital/input */ 0, 31, /* min, max value */ -6200, 0, 200, /* min, max, step - dB */ SB_DSP4_PRIVATE( SB_DSP4_MASTER_DEV, 3, 3 ), NULL, /* compute dB -> linear */ NULL, /* compute linear -> dB */ NULL, /* record source */ NULL, /* set mute */ snd_sb16mixer_volume_level, /* set volume level */ }, { SND_MIXER_PRI_BASS, /* priority */ SND_MIXER_PRI_MASTER, /* parent priority */ SND_MIXER_ID_BASS, /* device name */ SND_MIXER_OSS_BASS, /* OSS device # */ 0, 1, 0, 0, 0, /* mute/stereo/record/digital/input */ 0, 15, /* min, max value */ -1400, 1400, 200, /* min, max, step - dB */ SB_DSP4_PRIVATE( SB_DSP4_BASS_DEV, 4, 4 ), NULL, /* compute dB -> linear */ NULL, /* compute linear -> dB */ NULL, /* record source */ NULL, /* set mute */ snd_sb16mixer_volume_level, /* set volume level */ }, { SND_MIXER_PRI_TREBLE, /* priority */ SND_MIXER_PRI_MASTER, /* parent priority */ SND_MIXER_ID_TREBLE, /* device name */ SND_MIXER_OSS_TREBLE, /* OSS device # */ 0, 1, 0, 0, 0, /* mute/stereo/record/digital/input */ 0, 15, /* min, max value */ -1400, 1400, 200, /* min, max, step - dB */ SB_DSP4_PRIVATE( SB_DSP4_TREBLE_DEV, 4, 4 ), NULL, /* compute dB -> linear */ NULL, /* compute linear -> dB */ NULL, /* record source */ NULL, /* set mute */ snd_sb16mixer_volume_level, /* set volume level */ }, { SND_MIXER_PRI_SYNTHESIZER, /* priority */ SND_MIXER_PRI_MASTER, /* parent priority */ SND_MIXER_ID_SYNTHESIZER, /* device name */ SND_MIXER_OSS_SYNTH, /* OSS device # */ 0, 1, 1, 0, 0, /* mute/stereo/record/digital/input */ 0, 31, /* min, max value */ -6200, 0, 200, /* min, max, step - dB */ SB_DSP4_PRIVATE( SB_DSP4_SYNTH_DEV, 3, 3 ), NULL, /* compute dB -> linear */ NULL, /* compute linear -> dB */ snd_sb16mixer_record_source,/* record source */ NULL, /* set mute */ snd_sb16mixer_volume_level, /* set volume level */ }, { SND_MIXER_PRI_PCM, /* priority */ SND_MIXER_PRI_MASTER, /* parent priority */ SND_MIXER_ID_PCM, /* device name */ SND_MIXER_OSS_PCM, /* OSS device # */ 0, 1, 0, 0, 0, /* mute/stereo/record/digital/input */ 0, 31, /* max value */ -6200, 0, 200, /* min, max, step - dB */ SB_DSP4_PRIVATE( SB_DSP4_PCM_DEV, 3, 3 ), NULL, /* compute dB -> linear */ NULL, /* compute linear -> dB */ NULL, /* record source */ NULL, /* set mute */ snd_sb16mixer_volume_level, /* set volume level */ }, { SND_MIXER_PRI_SPEAKER, /* priority */ SND_MIXER_PRI_PARENT, /* parent priority */ SND_MIXER_ID_SPEAKER, /* device name */ SND_MIXER_OSS_SPEAKER, /* OSS device # */ 0, 0, 0, 0, 0, /* mute/stereo/record/digital/input */ 0, 3, /* max value */ -1800, 0, 600, /* min, max, step - dB */ SB_DSP4_PRIVATE( SB_DSP4_SPEAKER_DEV, 6, 6 ), NULL, /* compute dB -> linear */ NULL, /* compute linear -> dB */ NULL, /* record source */ NULL, /* set mute */ snd_sb16mixer_volume_level, /* set volume level */ }, { SND_MIXER_PRI_LINE, /* priority */ SND_MIXER_PRI_PARENT, /* parent priority */ SND_MIXER_ID_LINE, /* device name */ SND_MIXER_OSS_LINE, /* OSS device # */ 1, 1, 1, 0, 1, /* mute/stereo/record/digital/input */ 0, 31, /* max value */ -6200, 0, 200, /* min, max, step - dB */ SB_DSP4_PRIVATE( SB_DSP4_LINE_DEV, 3, 3 ), NULL, /* compute dB -> linear */ NULL, /* compute linear -> dB */ snd_sb16mixer_record_source,/* record source */ snd_sb16mixer_mute, /* set mute */ snd_sb16mixer_volume_level, /* set volume level */ }, { SND_MIXER_PRI_MIC, /* priority */ SND_MIXER_PRI_PARENT, /* parent priority */ SND_MIXER_ID_MIC, /* device name */ SND_MIXER_OSS_MIC, /* OSS device # */ 1, 0, 1, 0, 1, /* mute/stereo/record/digital/input */ 0, 31, /* max value */ -6200, 0, 200, /* min, max, step - dB */ SB_DSP4_PRIVATE( SB_DSP4_MIC_DEV, 3, 3 ), NULL, /* compute dB -> linear */ NULL, /* compute linear -> dB */ snd_sb16mixer_record_source,/* record source */ snd_sb16mixer_mute, /* set mute */ snd_sb16mixer_volume_level, /* set volume level */ }, { SND_MIXER_PRI_CD, /* priority */ SND_MIXER_PRI_MASTER, /* parent priority */ SND_MIXER_ID_CD, /* device name */ SND_MIXER_OSS_CD, /* OSS device # */ 1, 1, 1, 0, 1, /* mute/stereo/record/digital/input */ 0, 31, /* max value */ -6200, 0, 200, /* min, max, step - dB */ SB_DSP4_PRIVATE( SB_DSP4_CD_DEV, 3, 3 ), NULL, /* compute dB -> linear */ NULL, /* compute linear -> dB */ snd_sb16mixer_record_source,/* record source */ snd_sb16mixer_mute, /* set mute */ snd_sb16mixer_volume_level, /* set volume level */ }, { SND_MIXER_PRI_IGAIN, /* priority */ SND_MIXER_PRI_PARENT, /* parent priority */ SND_MIXER_ID_IGAIN, /* device name */ SND_MIXER_OSS_IGAIN, /* OSS device # */ 0, 1, 0, 0, 1, /* mute/stereo/record/digital/input */ 0, 3, /* max value */ -1800, 0, 600, /* min, max, step - dB */ SB_DSP4_PRIVATE( SB_DSP4_IGAIN_DEV, 6, 6 ), NULL, /* compute dB -> linear */ NULL, /* compute linear -> dB */ NULL, /* record source */ NULL, /* set mute */ snd_sb16mixer_volume_level, /* set volume level */ }, { SND_MIXER_PRI_OGAIN, /* priority */ SND_MIXER_PRI_PARENT, /* parent priority */ SND_MIXER_ID_OGAIN, /* device name */ SND_MIXER_OSS_OGAIN, /* OSS device # */ 0, 1, 0, 0, 0, /* mute/stereo/record/digital/input */ 0, 3, /* max value */ -1800, 0, 600, /* min, max, step - dB */ SB_DSP4_PRIVATE( SB_DSP4_OGAIN_DEV, 6, 6 ), NULL, /* compute dB -> linear */ NULL, /* compute linear -> dB */ NULL, /* record source */ NULL, /* set mute */ snd_sb16mixer_volume_level, /* set volume level */ } }; snd_kmixer_t *snd_sbdsp_new_mixer( snd_card_t *card, sbmixer_t *sbmix, unsigned short hardware ) { int idx, count; snd_kmixer_t *mixer; snd_kmixer_channel_t *channel; struct snd_stru_mixer_channel_hw *phw; unsigned long flags; char *name; switch ( hardware ) { case SB_HW_PRO: count = SB_DSP_MIXS; phw = snd_sbmixer_pro_mixs; name = "CTL1345"; break; case SB_HW_16: count = SB_DSP4_MIXS; phw = snd_sbmixer_16_mixs; name = "CTL1745"; break; default: return NULL; } if ( !card ) return NULL; mixer = snd_mixer_new( card, name ); if ( !mixer ) return NULL; strcpy( mixer -> name, mixer -> id ); mixer -> private_data = sbmix; for ( idx = 0; idx < count; idx++ ) { channel = snd_mixer_new_channel( mixer, &phw[ idx ] ); if ( !channel ) { snd_mixer_free( mixer ); return NULL; } } mixer -> hw.caps = SND_MIXER_INFO_CAP_EXCL_RECORD; snd_spin_lock( sbmix, mixer, &flags ); snd_sbmixer_write( sbmix, 0x00, 0x00 ); /* mixer reset */ snd_spin_unlock( sbmix, mixer, &flags ); return mixer; } .