/*
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *  Routines for control of Sound Blaster 16 mixer
 *
 *
 *   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 "../../include/driver.h"
#include "../../include/sb.h"

void snd_sb16mixer_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_sb16mixer_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;
}

static int snd_sb16mixer_input_route(sbmixer_t * sbmix,
				     int w_flag,
				     unsigned int *prsw,
				     unsigned char left_bit,
				     unsigned char right_bit)
{
	unsigned long flags;
	unsigned char oleft, oright;
	int change = 0, tmp;
	
	left_bit = 1 << left_bit;
	right_bit = 1 << right_bit;
	spin_lock_irqsave(&sbmix->lock, flags);
	oleft = snd_sb16mixer_read(sbmix, SB_DSP4_INPUT_LEFT);
	oright = snd_sb16mixer_read(sbmix, SB_DSP4_INPUT_RIGHT);
	if (!w_flag) {
		snd_mixer_set_bit(prsw, 0, oleft & left_bit);
		snd_mixer_set_bit(prsw, 1, oright & left_bit);
		snd_mixer_set_bit(prsw, 2, oleft & right_bit);
		snd_mixer_set_bit(prsw, 3, oright & right_bit);
	} else {
		tmp = snd_mixer_get_bit(prsw, 0);
		if (tmp != ((oleft & left_bit) != 0)) {
			change = 1;
			oleft &= ~left_bit;
			if (tmp)
				oleft |= left_bit;
		}
		tmp = snd_mixer_get_bit(prsw, 1);
		if (tmp != ((oright & left_bit) != 0)) {
			change = 1;
			oright &= ~left_bit;
			if (tmp)
				oright |= left_bit;
		}
		tmp = snd_mixer_get_bit(prsw, 2);
		if (tmp != ((oleft & right_bit) != 0)) {
			change = 1;
			oleft &= ~right_bit;
			if (tmp)
				oleft |= right_bit;
		}
		tmp = snd_mixer_get_bit(prsw, 3);
		if (tmp != ((oright & right_bit) != 0)) {
			change = 1;
			oright &= ~right_bit;
			if (tmp)
				oright |= right_bit;
		}
		snd_sb16mixer_write(sbmix, SB_DSP4_INPUT_LEFT, oleft);
		snd_sb16mixer_write(sbmix, SB_DSP4_INPUT_RIGHT, oright);
	}
	spin_unlock_irqrestore(&sbmix->lock, flags);
	return change;
}

static int snd_sb16mixer_cd_input_switch(void *private_data,
					 int w_flag, unsigned int *prsw)
{
	return snd_sb16mixer_input_route((sbmixer_t *)private_data, w_flag,
					 prsw, 2, 1);
}

static int snd_sb16mixer_line_input_switch(void *private_data,
					   int w_flag, unsigned int *prsw)
{
	return snd_sb16mixer_input_route((sbmixer_t *)private_data, w_flag,
					 prsw, 4, 3);
}

static int snd_sb16mixer_midi_input_switch(void *private_data,
					   int w_flag, unsigned int *prsw)
{
	return snd_sb16mixer_input_route((sbmixer_t *)private_data, w_flag,
					 prsw, 6, 5);
}

static int snd_sb16mixer_mic_input_switch(void *private_data,
					  int w_flag, unsigned int *bitmap)
{
	sbmixer_t *sbmix = (sbmixer_t *)private_data;
	unsigned long flags;
	unsigned char lreg, rreg, oleft, oright;
	int change = 0;
	
	spin_lock_irqsave(&sbmix->lock, flags);
	oleft = (lreg = snd_sb16mixer_read(sbmix, SB_DSP4_INPUT_LEFT)) & 1;
	oright = (rreg = snd_sb16mixer_read(sbmix, SB_DSP4_INPUT_RIGHT)) & 1;
	if (!w_flag) {
		snd_mixer_set_bit(bitmap, 0, oleft);
		snd_mixer_set_bit(bitmap, 1, oright);
	} else {
		change = oleft != snd_mixer_get_bit(bitmap, 0) ||
		         oright != snd_mixer_get_bit(bitmap, 1);
		lreg &= ~1;
		lreg |= snd_mixer_get_bit(bitmap, 0) ? 1 : 0;
		rreg &= ~1;
		rreg |= snd_mixer_get_bit(bitmap, 1) ? 1 : 0;
		snd_sb16mixer_write(sbmix, SB_DSP4_INPUT_LEFT, lreg);
		snd_sb16mixer_write(sbmix, SB_DSP4_INPUT_RIGHT, rreg);
	}
	spin_unlock_irqrestore(&sbmix->lock, flags);
	return change;
}

static int snd_sb16mixer_mic_output_switch(void *private_data,
					   int w_flag, unsigned int *bitmap)
{
	sbmixer_t *sbmix = (sbmixer_t *)private_data;
	unsigned long flags;
	unsigned char old, oreg;
	int change = 0;
	
	spin_lock_irqsave(&sbmix->lock, flags);
	old = (oreg = snd_sb16mixer_read(sbmix, SB_DSP4_OUTPUT_SW)) & 1;
	if (!w_flag) {
		snd_mixer_set_bit(bitmap, 0, old);
	} else {
		change = old != snd_mixer_get_bit(bitmap, 0);
		oreg &= ~1;
		oreg |= snd_mixer_get_bit(bitmap, 0) ? 1 : 0;
		snd_sb16mixer_write(sbmix, SB_DSP4_OUTPUT_SW, oreg);
	}
	spin_unlock_irqrestore(&sbmix->lock, flags);
	return change;
}

static int snd_sb16mixer_cd_output_switch(void *private_data,
					  int w_flag,
					  unsigned int *bitmap)
{
	sbmixer_t *sbmix = (sbmixer_t *)private_data;
	unsigned long flags;
	unsigned char oreg, oleft, oright;
	int change = 0;
	
	spin_lock_irqsave(&sbmix->lock, flags);
	oreg = snd_sb16mixer_read(sbmix, SB_DSP4_OUTPUT_SW);
	oleft = (oreg & 4) ? 1 : 0;
	oright = (oreg & 2) ? 1 : 0;
	if (!w_flag) {
		snd_mixer_set_bit(bitmap, 0, oleft);
		snd_mixer_set_bit(bitmap, 1, oright);
	} else {
		change = oleft != snd_mixer_get_bit(bitmap, 0) ||
		         oright != snd_mixer_get_bit(bitmap, 1);
		oreg &= ~6;
		oreg |= snd_mixer_get_bit(bitmap, 0) ? 4 : 0;
		oreg |= snd_mixer_get_bit(bitmap, 1) ? 2 : 0;
		snd_sb16mixer_write(sbmix, SB_DSP4_OUTPUT_SW, oreg);
	}
	spin_unlock_irqrestore(&sbmix->lock, flags);
	return change;
}

static int snd_sb16mixer_line_output_switch(void *private_data,
					    int w_flag,
					    unsigned int *bitmap)
{
	sbmixer_t *sbmix = (sbmixer_t *)private_data;
	unsigned long flags;
	unsigned char oreg, oleft, oright;
	int change = 0;
	
	spin_lock_irqsave(&sbmix->lock, flags);
	oreg = snd_sb16mixer_read(sbmix, SB_DSP4_OUTPUT_SW);
	oleft = (oreg & 0x10) ? 1 : 0;
	oright = (oreg & 8) ? 1 : 0;
	if (!w_flag) {
		snd_mixer_set_bit(bitmap, 0, oleft);
		snd_mixer_set_bit(bitmap, 1, oright);
	} else {
		change = oleft != snd_mixer_get_bit(bitmap, 0) ||
		         oright != snd_mixer_get_bit(bitmap, 1);
		oreg &= ~0x18;
		oreg |= snd_mixer_get_bit(bitmap, 0) ? 0x10 : 0;
		oreg |= snd_mixer_get_bit(bitmap, 1) ? 8 : 0;
		snd_sb16mixer_write(sbmix, SB_DSP4_OUTPUT_SW, oreg);
	}
	spin_unlock_irqrestore(&sbmix->lock, flags);
	return change;
}

static int snd_sb16mixer_volume_level(sbmixer_t *sbmix,
				      int w_flag, int *voices,
				      unsigned char max, 
				      unsigned char shift,
				      unsigned char reg)
{
	unsigned long flags;
	unsigned char lreg, rreg, oleft, oright;
	int change = 0;
	
	spin_lock_irqsave(&sbmix->lock, flags);
	oleft = ((lreg = snd_sb16mixer_read(sbmix, reg + 0)) >> shift) & max;
	oright = ((rreg = snd_sb16mixer_read(sbmix, reg + 1)) >> shift) & max;
	if (!w_flag) {
		voices[0] = oleft;
		voices[1] = oright;
	} else {
		change = oleft != voices[0] || oright != voices[1];
		lreg &= ~(max << shift);
		lreg |= voices[0] << shift;
		rreg &= ~(max << shift);
		rreg |= voices[1] << shift;
		snd_sb16mixer_write(sbmix, reg + 0, lreg);
		snd_sb16mixer_write(sbmix, reg + 1, rreg);
	}
	spin_unlock_irqrestore(&sbmix->lock, flags);
	return change;
	
}

static int snd_sb16mixer_mono_volume_level(sbmixer_t *sbmix,
					   int w_flag, int *voices,
					   unsigned char max,
					   unsigned char shift,
					   unsigned char reg)
{
	unsigned long flags;
	unsigned char oreg, oval;
	int change = 0;
	
	spin_lock_irqsave(&sbmix->lock, flags);
	oval = ((oreg = snd_sb16mixer_read(sbmix, reg)) >> shift) & max;
	if (!w_flag) {
		voices[0] = oval;
	} else {
		change = oval != voices[0];
		oreg &= ~(max << shift);
		oreg |= voices[0] << shift;
		snd_sb16mixer_write(sbmix, reg, oreg);
	}
	spin_unlock_irqrestore(&sbmix->lock, flags);
	return change;
	
}

static int snd_sb16mixer_master_volume_level(void *private_data, int w_flag, int *voices)
{
	return snd_sb16mixer_volume_level((sbmixer_t *)private_data,
					  w_flag, voices,
					  31, 3, SB_DSP4_MASTER_DEV);
}

static int snd_sb16mixer_pcm_volume_level(void *private_data, int w_flag, int *voices)
{
	return snd_sb16mixer_volume_level((sbmixer_t *)private_data,
					  w_flag, voices,
					  31, 3, SB_DSP4_PCM_DEV);
}

static int snd_sb16mixer_synth_volume_level(void *private_data, int w_flag, int *voices)
{
	return snd_sb16mixer_volume_level((sbmixer_t *)private_data,
					  w_flag, voices,
					  31, 3, SB_DSP4_SYNTH_DEV);
}

static int snd_sb16mixer_cd_volume_level(void *private_data, int w_flag, int *voices)
{
	return snd_sb16mixer_volume_level((sbmixer_t *)private_data,
					  w_flag, voices,
					  31, 3, SB_DSP4_CD_DEV);
}

static int snd_sb16mixer_line_volume_level(void *private_data, int w_flag, int *voices)
{
	return snd_sb16mixer_volume_level((sbmixer_t *)private_data,
					  w_flag, voices,
					  31, 3, SB_DSP4_LINE_DEV);
}

static int snd_sb16mixer_mic_volume_level(void *private_data, int w_flag, int *voices)
{
	return snd_sb16mixer_mono_volume_level((sbmixer_t *)private_data,
					       w_flag, voices,
					       31, 3, SB_DSP4_MIC_DEV);
}

static int snd_sb16mixer_speaker_volume_level(void *private_data, int w_flag, int *voices)
{
	return snd_sb16mixer_mono_volume_level((sbmixer_t *)private_data,
					       w_flag, voices,
					       3, 6, SB_DSP4_SPEAKER_DEV);
}

static int snd_sb16mixer_igain_volume_level(void *private_data, int w_flag, int *voices)
{
	return snd_sb16mixer_volume_level((sbmixer_t *)private_data,
					  w_flag, voices,
					  3, 6, SB_DSP4_IGAIN_DEV);
}

static int snd_sb16mixer_ogain_volume_level(void *private_data, int w_flag, int *voices)
{
	return snd_sb16mixer_volume_level((sbmixer_t *)private_data,
					  w_flag, voices,
					  3, 6, SB_DSP4_OGAIN_DEV);
}

static int snd_sb16mixer_tone_control(void *private_data,
				      int w_flag,
				      struct snd_mixer_element_tone_control1 *tc1)
{
	sbmixer_t *sbmix = (sbmixer_t *)private_data;
	unsigned long flags;
	unsigned char obass, otreble;
	int change = 0;
	
	spin_lock_irqsave(&sbmix->lock, flags);
	obass = snd_sb16mixer_read(sbmix, SB_DSP4_BASS_DEV);
	otreble = snd_sb16mixer_read(sbmix, SB_DSP4_TREBLE_DEV);
	if (w_flag) {
		if (tc1->tc & SND_MIXER_TC1_BASS) {
			change |= ((obass >> 4) & 15) != tc1->bass;
			obass &= 0x0f;
			obass |= (tc1->bass & 15) << 4;
			snd_sb16mixer_write(sbmix, SB_DSP4_BASS_DEV + 0, obass);
			snd_sb16mixer_write(sbmix, SB_DSP4_BASS_DEV + 1, obass);
		}
		if (tc1->tc & SND_MIXER_TC1_TREBLE) {
			change |= ((otreble >> 4) & 15) != tc1->treble;
			otreble &= 0x0f;
			otreble |= (tc1->treble & 15) << 4;
			snd_sb16mixer_write(sbmix, SB_DSP4_TREBLE_DEV + 0, otreble);
			snd_sb16mixer_write(sbmix, SB_DSP4_TREBLE_DEV + 1, otreble);
		}
	} else {
		tc1->tc = SND_MIXER_TC1_BASS | SND_MIXER_TC1_TREBLE;
		tc1->bass = (obass >> 4) & 15;
		tc1->treble = (otreble >> 4) & 15;
	}
	spin_unlock_irqrestore(&sbmix->lock, flags);
	return change;
}

static int snd_sb16mixer_get_mic_agc_switch(snd_kmixer_t * mixer,
				    snd_kswitch_t * kswitch,
				    snd_switch_t * uswitch)
{
	unsigned long flags;
	sbmixer_t *sbmix;

	sbmix = (sbmixer_t *) kswitch->private_data;
	uswitch->type = SND_SW_TYPE_BOOLEAN;
	spin_lock_irqsave(&sbmix->lock, flags);
	uswitch->value.enable =
	    snd_sb16mixer_read(sbmix, SB_DSP4_MIC_AGC) & 0x01 ? 1 : 0;
	spin_unlock_irqrestore(&sbmix->lock, flags);
	return 0;
}

static int snd_sb16mixer_set_mic_agc_switch(snd_kmixer_t * mixer,
				    snd_kswitch_t * kswitch,
				    snd_switch_t * uswitch)
{
	unsigned long flags;
	sbmixer_t *sbmix;
	unsigned char reg, val;
	int change = 0;

	sbmix = (sbmixer_t *) kswitch->private_data;
	if (uswitch->type != SND_SW_TYPE_BOOLEAN)
		return -EINVAL;
	val = uswitch->value.enable ? 0x01 : 0x00;
	spin_lock_irqsave(&sbmix->lock, flags);
	reg = snd_sb16mixer_read(sbmix, SB_DSP4_MIC_AGC);
	change = (reg & 0x01) != val;
	snd_sb16mixer_write(sbmix, SB_DSP4_MIC_AGC, (reg & ~0x01) | val);
	spin_unlock_irqrestore(&sbmix->lock, flags);
	return change;
}

snd_kswitch_t snd_sb16mixer_mic_agc_switch =
{
	"Auto Gain Control",
	(snd_get_switch_t *)snd_sb16mixer_get_mic_agc_switch,
	(snd_set_switch_t *)snd_sb16mixer_set_mic_agc_switch,
	0,
	NULL,
	NULL
};

static int snd_sb16mixer_get_3d_stereo_enh_switch(snd_kmixer_t *mixer, snd_kswitch_t *kswitch, snd_switch_t *uswitch)
{
	unsigned long flags;
	sbmixer_t *sbmix;
  
	sbmix = (sbmixer_t *)kswitch->private_data;
	uswitch->type = SND_SW_TYPE_BOOLEAN;
	spin_lock_irqsave(&sbmix->lock, flags);
	uswitch->value.enable =
		snd_sb16mixer_read(sbmix, SB_DSP4_3D) & 0x01 ? 1 : 0;
	/*printk("snd-sbawe: get switch %d\n", uswitch->value.enable);*/
	spin_unlock_irqrestore(&sbmix->lock, flags);
	return 0;
}

static int snd_sb16mixer_set_3d_stereo_enh_switch(snd_kmixer_t *mixer, snd_kswitch_t *kswitch, snd_switch_t *uswitch)
{
	unsigned long flags;
	sbmixer_t *sbmix;
	int change = 0;
	unsigned char reg, val;
	
	sbmix = (sbmixer_t *)kswitch->private_data;
	if (uswitch->type != SND_SW_TYPE_BOOLEAN)
		return -EINVAL;
	val = uswitch->value.enable ? 0x01 : 0x00;
	spin_lock_irqsave(&sbmix->lock, flags);
	reg = snd_sb16mixer_read(sbmix, SB_DSP4_3D);
	change = (reg & 0x01) != val ? 1 : 0;
	snd_sb16mixer_write(sbmix, SB_DSP4_3D, (reg & ~0x01) | val);
	spin_unlock_irqrestore(&sbmix->lock, flags);
	return change;
}

snd_kswitch_t snd_sb16mixer_3d_stereo_enh_switch = {
	"3D Stereo Enhancement",
	(snd_get_switch_t *)snd_sb16mixer_get_3d_stereo_enh_switch,
	(snd_set_switch_t *)snd_sb16mixer_set_3d_stereo_enh_switch,
	0,
	NULL,
	NULL
};

static int snd_sb16mixer_group_ctrl_stereo(sbmixer_t *mixer,
					   snd_kmixer_file_t *file,
					   int w_flag,
					   snd_mixer_group_t *ugroup,
					   snd_mixer_volume1_control_t *volume1,
					   snd_kmixer_element_t *volume1_element,
					   int max,
					   snd_mixer_sw1_control_t *sw1_output,
					   snd_kmixer_element_t *sw1_output_element,
					   snd_mixer_sw1_control_t *sw1_input,
					   snd_kmixer_element_t *sw1_input_element,
					   snd_mixer_sw3_control_t *sw3_input,
					   snd_kmixer_element_t *sw3_input_element)
{
	int voices[2];
	unsigned int bitmap;
	int change = 0;

	if (!w_flag) {
		ugroup->caps = 0;
		ugroup->channels = SND_MIXER_CHN_MASK_STEREO;
		if (volume1) {
			ugroup->caps |= SND_MIXER_GRPCAP_VOLUME;
			volume1(mixer, 0, voices);
			ugroup->volume.names.front_left = voices[0];
			ugroup->volume.names.front_right = voices[1];
			ugroup->min = 0;
			ugroup->max = max;
		}
		if (sw1_output) {
			ugroup->caps |= SND_MIXER_GRPCAP_MUTE;
			sw1_output(mixer, 0, &bitmap);
			ugroup->mute = 0;
			if(!snd_mixer_get_bit(&bitmap, 0))
				ugroup->mute |= SND_MIXER_CHN_MASK_FRONT_LEFT;
			if(!snd_mixer_get_bit(&bitmap, 1))
				ugroup->mute |= SND_MIXER_CHN_MASK_FRONT_RIGHT;
		}
		if (sw1_input) {
			ugroup->caps |= SND_MIXER_GRPCAP_CAPTURE;
			sw1_input(mixer, 0, &bitmap);
			ugroup->capture = 0;
			if (snd_mixer_get_bit(&bitmap, 0))
				ugroup->capture |= SND_MIXER_CHN_MASK_FRONT_LEFT;
			if (snd_mixer_get_bit(&bitmap, 1))
				ugroup->capture |= SND_MIXER_CHN_MASK_FRONT_RIGHT;
		} else if (sw3_input) {
			ugroup->caps |= SND_MIXER_GRPCAP_CAPTURE;
			sw3_input(mixer, 0, &bitmap);
			ugroup->capture = 0;
			if (snd_mixer_get_bit(&bitmap, 0))
				ugroup->capture |= SND_MIXER_CHN_MASK_FRONT_LEFT;
			if (snd_mixer_get_bit(&bitmap, 3))
				ugroup->capture |= SND_MIXER_CHN_MASK_FRONT_RIGHT;
		}
	} else {
		if (volume1) {
			voices[0] = ugroup->volume.names.front_left & max;
			voices[1] = ugroup->volume.names.front_right & max;
			if (volume1(mixer, 1, voices) > 0) {
				snd_mixer_element_value_change(file, volume1_element, 0);
				change = 1;
			}
		}
		if (sw1_output) {
			bitmap = 0;
			if (!(ugroup->mute & SND_MIXER_CHN_MASK_FRONT_LEFT))
				snd_mixer_set_bit(&bitmap, 0, 1);
			if (!(ugroup->mute & SND_MIXER_CHN_MASK_FRONT_RIGHT))
				snd_mixer_set_bit(&bitmap, 1, 1);
			if (sw1_output(mixer, 1, &bitmap) > 0) {
				snd_mixer_element_value_change(file, sw1_output_element, 0);
				change = 1;
			}
		}
		if (sw1_input) {
			bitmap = 0;
			if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_LEFT)
				snd_mixer_set_bit(&bitmap, 0, 1);
			if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_RIGHT)
				snd_mixer_set_bit(&bitmap, 1, 1);
			if (sw1_input(mixer, 1, &bitmap) > 0) {
				snd_mixer_element_value_change(file, sw1_input_element, 0);
				change = 1;
			}
		} else if (sw3_input) {
			bitmap = 0;
			if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_LEFT)
				snd_mixer_set_bit(&bitmap, 0, 1);
			if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_RIGHT)
				snd_mixer_set_bit(&bitmap, 3, 1);
			if (sw3_input(mixer, 1, &bitmap) > 0) {
				snd_mixer_element_value_change(file, sw3_input_element, 0);
				change = 1;
			}
		}
	}
	return change;
}

static int snd_sb16mixer_group_ctrl_mono(sbmixer_t *mixer,
					 snd_kmixer_file_t *file,
					 int w_flag,
					 snd_mixer_group_t *ugroup,
					 snd_mixer_volume1_control_t *volume1,
					 snd_kmixer_element_t *volume1_element,
					 int max,
					 snd_mixer_sw1_control_t *sw1_output,
					 snd_kmixer_element_t *sw1_output_element,
					 snd_mixer_sw1_control_t *sw1_input,
					 snd_kmixer_element_t *sw1_input_element)
{
	int voice;
	unsigned int bitmap;
	int change = 0;

	if (!w_flag) {
		ugroup->caps = 0;
		ugroup->channels = SND_MIXER_CHN_MASK_STEREO;
		if (volume1) {
			ugroup->caps |= SND_MIXER_GRPCAP_VOLUME | SND_MIXER_GRPCAP_JOINTLY_VOLUME;
			volume1(mixer, 0, &voice);
			ugroup->volume.names.front_left = voice;
			ugroup->volume.names.front_right = voice;
			ugroup->min = 0;
			ugroup->max = max;
		}
		if (sw1_output) {
			ugroup->caps |= SND_MIXER_GRPCAP_MUTE;
			sw1_output(mixer, 0, &bitmap);
			ugroup->mute = 0;
			if(!snd_mixer_get_bit(&bitmap, 0))
				ugroup->mute |= SND_MIXER_CHN_MASK_FRONT_LEFT;
			if(!snd_mixer_get_bit(&bitmap, 1))
				ugroup->mute |= SND_MIXER_CHN_MASK_FRONT_RIGHT;
		}
		if (sw1_input) {
			ugroup->caps |= SND_MIXER_GRPCAP_CAPTURE;
			sw1_input(mixer, 0, &bitmap);
			ugroup->capture = 0;
			if (snd_mixer_get_bit(&bitmap, 0))
				ugroup->capture |= SND_MIXER_CHN_MASK_FRONT_LEFT;
			if (snd_mixer_get_bit(&bitmap, 1))
				ugroup->capture |= SND_MIXER_CHN_MASK_FRONT_RIGHT;
		}
	} else {
		if (volume1) {
			voice = ugroup->volume.names.front_left & max;
			if (volume1(mixer, 1, &voice) > 0) {
				snd_mixer_element_value_change(file, volume1_element, 0);
				change = 1;
			}
		}
		if (sw1_output) {
			bitmap = 0;
			if (!(ugroup->mute & SND_MIXER_CHN_MASK_FRONT_LEFT))
				snd_mixer_set_bit(&bitmap, 0, 1);
			if (!(ugroup->mute & SND_MIXER_CHN_MASK_FRONT_RIGHT))
				snd_mixer_set_bit(&bitmap, 1, 1);
			if (sw1_output(mixer, 1, &bitmap) > 0) {
				snd_mixer_element_value_change(file, sw1_output_element, 0);
				change = 1;
			}
		}
		if (sw1_input) {
			bitmap = 0;
			if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_LEFT)
				snd_mixer_set_bit(&bitmap, 0, 1);
			if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_RIGHT)
				snd_mixer_set_bit(&bitmap, 1, 1);
			if (sw1_input(mixer, 1, &bitmap) > 0) {
				snd_mixer_element_value_change(file, sw1_input_element, 0);
				change = 1;
			}
		}
	}
	return change;
}

static int snd_sb16mixer_group_igain(void *private_data,
				     snd_kmixer_file_t *file,
				     int w_flag,
				     snd_mixer_group_t *ugroup)
{
	sbmixer_t *mixer = (sbmixer_t *)private_data;

	return snd_sb16mixer_group_ctrl_stereo(mixer, file, w_flag, ugroup,
					       snd_sb16mixer_igain_volume_level,
					       mixer->me_vol_igain,
					       3,
					       NULL,
					       NULL,
					       NULL,
					       NULL,
					       NULL,
					       NULL);
}

static int snd_sb16mixer_group_speaker(void *private_data,
				       snd_kmixer_file_t *file,
				       int w_flag,
				       snd_mixer_group_t *ugroup)
{
	sbmixer_t *mixer = (sbmixer_t *)private_data;

	return snd_sb16mixer_group_ctrl_mono(mixer, file, w_flag, ugroup,
					     snd_sb16mixer_speaker_volume_level,
					     mixer->me_vol_speaker,
					     3,
					     NULL,
					     NULL,
					     NULL,
					     NULL);
}

static int snd_sb16mixer_group_mic(void *private_data,
				   snd_kmixer_file_t *file,
				   int w_flag,
				   snd_mixer_group_t *ugroup)
{
	sbmixer_t *mixer = (sbmixer_t *)private_data;

	return snd_sb16mixer_group_ctrl_mono(mixer, file, w_flag, ugroup,
					     snd_sb16mixer_mic_volume_level,
					     mixer->me_vol_mic,
					     31,
					     snd_sb16mixer_mic_output_switch,
					     mixer->me_sw1_mic_output,
					     snd_sb16mixer_mic_input_switch,
					     mixer->me_sw1_mic_input);
}

static int snd_sb16mixer_group_line(void *private_data,
				    snd_kmixer_file_t *file,
				    int w_flag,
				    snd_mixer_group_t *ugroup)
{
	sbmixer_t *mixer = (sbmixer_t *)private_data;

	return snd_sb16mixer_group_ctrl_stereo(mixer, file, w_flag, ugroup,
					       snd_sb16mixer_line_volume_level,
					       mixer->me_vol_line,
					       31,
					       snd_sb16mixer_line_output_switch,
					       mixer->me_sw1_line_output,
					       NULL,
					       NULL,
					       snd_sb16mixer_line_input_switch,
					       mixer->me_sw3_line_input);
}

static int snd_sb16mixer_group_cd(void *private_data,
				  snd_kmixer_file_t *file,
				  int w_flag,
				  snd_mixer_group_t *ugroup)
{
	sbmixer_t *mixer = (sbmixer_t *)private_data;

	return snd_sb16mixer_group_ctrl_stereo(mixer, file, w_flag, ugroup,
					       snd_sb16mixer_cd_volume_level,
					       mixer->me_vol_cd,
					       31,
					       snd_sb16mixer_cd_output_switch,
					       mixer->me_sw1_cd_output,
					       NULL,
					       NULL,
					       snd_sb16mixer_cd_input_switch,
					       mixer->me_sw3_cd_input);
}

static int snd_sb16mixer_group_synth(void *private_data,
				     snd_kmixer_file_t *file,
				     int w_flag,
				     snd_mixer_group_t *ugroup)
{
	sbmixer_t *mixer = (sbmixer_t *)private_data;

	return snd_sb16mixer_group_ctrl_stereo(mixer, file, w_flag, ugroup,
					       snd_sb16mixer_synth_volume_level,
					       mixer->me_vol_synth,
					       31,
					       NULL,
					       NULL,
					       NULL,
					       NULL,
					       snd_sb16mixer_midi_input_switch,
					       mixer->me_sw3_synth_input);
}

static int snd_sb16mixer_group_pcm(void *private_data,
				   snd_kmixer_file_t *file,
				   int w_flag,
				   snd_mixer_group_t *ugroup)
{
	sbmixer_t *mixer = (sbmixer_t *)private_data;

	return snd_sb16mixer_group_ctrl_stereo(mixer, file, w_flag, ugroup,
					       snd_sb16mixer_pcm_volume_level,
					       mixer->me_vol_pcm,
					       31,
					       NULL,
					       NULL,
					       NULL,
					       NULL,
					       NULL,
					       NULL);
}

static int snd_sb16mixer_group_ogain(void *private_data,
				     snd_kmixer_file_t *file,
				     int w_flag,
				     snd_mixer_group_t *ugroup)
{
	sbmixer_t *mixer = (sbmixer_t *)private_data;

	return snd_sb16mixer_group_ctrl_stereo(mixer, file, w_flag, ugroup,
					       snd_sb16mixer_ogain_volume_level,
					       mixer->me_vol_ogain,
					       3,
					       NULL,
					       NULL,
					       NULL,
					       NULL,
					       NULL,
					       NULL);
}

static int snd_sb16mixer_group_bass(void *private_data,
				    snd_kmixer_file_t * file,
				    int w_flag,
				    snd_mixer_group_t * ugroup)
{
	sbmixer_t *sbmix = (sbmixer_t *)private_data;
	struct snd_mixer_element_tone_control1 tc;
	int change = 0;

	if (!w_flag) {
		ugroup->caps = SND_MIXER_GRPCAP_VOLUME;
		ugroup->channels = SND_MIXER_CHN_MASK_MONO;
		snd_sb16mixer_tone_control(sbmix, 0, &tc);
		ugroup->volume.names.front_left = tc.bass;
		ugroup->min = 0;
		ugroup->max = 15;
	} else {
		tc.tc = SND_MIXER_TC1_BASS;
		tc.bass = ugroup->volume.names.front_left % 15;
		if (snd_sb16mixer_tone_control(sbmix, 1, &tc) > 0) {
			snd_mixer_element_value_change(file, sbmix->me_tone, 0);
			change = 1;
		}
	}
	return change;
}

static int snd_sb16mixer_group_treble(void *private_data,
				    snd_kmixer_file_t * file,
				    int w_flag,
				    snd_mixer_group_t * ugroup)
{
	sbmixer_t *sbmix = (sbmixer_t *)private_data;
	struct snd_mixer_element_tone_control1 tc;
	int change = 0;

	if (!w_flag) {
		ugroup->caps = SND_MIXER_GRPCAP_VOLUME;
		ugroup->channels = SND_MIXER_CHN_MASK_MONO;
		snd_sb16mixer_tone_control(sbmix, 0, &tc);
		ugroup->volume.names.front_left = tc.treble;
		ugroup->min = 0;
		ugroup->max = 15;
	} else {
		tc.tc = SND_MIXER_TC1_TREBLE;
		tc.treble = ugroup->volume.names.front_left % 15;
		if (snd_sb16mixer_tone_control(sbmix, 1, &tc) > 0) {
			snd_mixer_element_value_change(file, sbmix->me_tone, 0);
			change = 1;
		}
	}
	return change;
}

static int snd_sb16mixer_group_master(void *private_data,
				      snd_kmixer_file_t *file,
				      int w_flag,
				      snd_mixer_group_t *ugroup)
{
	sbmixer_t *mixer = (sbmixer_t *)private_data;

	return snd_sb16mixer_group_ctrl_stereo(mixer, file, w_flag, ugroup,
					       snd_sb16mixer_master_volume_level,
					       mixer->me_vol_master,
					       31,
					       NULL,
					       NULL,
					       NULL,
					       NULL,
					       NULL,
					       NULL);
}

snd_kmixer_t *snd_sb16dsp_new_mixer(snd_card_t * card,
				    sbmixer_t * sbmix,
				    unsigned short hardware,
				    int pcm_dev)
{
	snd_kmixer_t *mixer;
	unsigned long flags;
	snd_kmixer_group_t *group, *group2;
	static struct snd_mixer_element_volume1_range db_range1[2] = {
		{0, 31, -6200, 0},
		{0, 31, -6200, 0}
	};
	static struct snd_mixer_element_volume1_range db_range2[1] = {
		{0, 3, -1800, 0}
	};
	static struct snd_mixer_element_volume1_range db_range3[2] = {
		{0, 3, 0, 1800},
		{0, 3, 0, 1800}
	};
	static snd_mixer_voice_t stereo_voices[2] = {
		{SND_MIXER_VOICE_LEFT, 0},
		{SND_MIXER_VOICE_RIGHT, 0}
	};
	static struct snd_mixer_element_tone_control1_info tone_control = {
		SND_MIXER_TC1_BASS | SND_MIXER_TC1_TREBLE,
		0, 15, -1400, 1400,
		0, 15, -1400, 1400
	};

	if (!card)
		return NULL;
	mixer = snd_mixer_new(card, "CTL1745");
	if (!mixer)
		return NULL;
	strcpy(mixer->name, mixer->id);

	spin_lock_irqsave(&sbmix->lock, flags);
	snd_sb16mixer_write(sbmix, 0x00, 0x00);	/* mixer reset */
	spin_unlock_irqrestore(&sbmix->lock, flags);

	/* build input and output accumulator */
	if ((sbmix->me_in_accu = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_INPUT_ACCU, 0, 0)) == NULL)
		goto __error;
	if ((sbmix->me_out_accu = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_OUTPUT_ACCU, 0, 0)) == NULL)
		goto __error;
	/* build master volume control */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_OUT_MASTER, 0, SND_MIXER_OSS_VOLUME, snd_sb16mixer_group_master, sbmix)) == NULL)
		goto __error;
	if ((sbmix->me_vol_master = snd_mixer_lib_volume1(mixer, "Master Volume", 0, 2, db_range1, snd_sb16mixer_master_volume_level, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, sbmix->me_vol_master) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_out_accu, sbmix->me_vol_master) < 0)
		goto __error;
	/* Tone control */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_GRP_BASS, 0, SND_MIXER_OSS_BASS, snd_sb16mixer_group_bass, sbmix)) == NULL)
		goto __error;
	if ((group2 = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_GRP_TREBLE, 0, SND_MIXER_OSS_TREBLE, snd_sb16mixer_group_treble, sbmix)) == NULL)
		goto __error;
	if ((sbmix->me_tone = snd_mixer_lib_tone_control1(mixer, SND_MIXER_ELEMENT_TONE_CONTROL, 0, &tone_control, snd_sb16mixer_tone_control, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, sbmix->me_tone) < 0)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group2, sbmix->me_tone) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_vol_master, sbmix->me_tone) < 0)
		goto __error;
	/* Output gain */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_GRP_OGAIN, 0, SND_MIXER_OSS_OGAIN, snd_sb16mixer_group_ogain, sbmix)) == NULL)
		goto __error;
	if ((sbmix->me_vol_ogain = snd_mixer_lib_volume1(mixer, SND_MIXER_GRP_OGAIN, 0, 2, db_range3, snd_sb16mixer_ogain_volume_level, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, sbmix->me_vol_ogain) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_tone, sbmix->me_vol_ogain) < 0)
		goto __error;
 	if ((sbmix->me_out_master = snd_mixer_lib_io_stereo(mixer, SND_MIXER_OUT_MASTER, 0, SND_MIXER_ETYPE_OUTPUT, 0)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_vol_ogain, sbmix->me_out_master) < 0)
		goto __error;
	/* PCM */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_PCM, 0, SND_MIXER_OSS_PCM, snd_sb16mixer_group_pcm, sbmix)) == NULL)
		goto __error;
 	if ((sbmix->me_playback = snd_mixer_lib_pcm(mixer, SND_MIXER_ELEMENT_PLAYBACK, 0, SND_MIXER_ETYPE_PLAYBACK, 1, &pcm_dev)) == NULL)
		goto __error;
	if ((sbmix->me_vol_pcm = snd_mixer_lib_volume1(mixer, "PCM Volume", 0, 2, db_range1, snd_sb16mixer_pcm_volume_level, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, sbmix->me_vol_pcm) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_playback, sbmix->me_vol_pcm) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_vol_pcm, sbmix->me_out_accu) < 0)
		goto __error;
	/* Synth */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_SYNTHESIZER, 0, SND_MIXER_OSS_SYNTH, snd_sb16mixer_group_synth, sbmix)) == NULL)
		goto __error;
 	if ((sbmix->me_in_synth = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_SYNTHESIZER, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((sbmix->me_vol_synth = snd_mixer_lib_volume1(mixer, "Synth Volume", 0, 2, db_range1, snd_sb16mixer_synth_volume_level, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, sbmix->me_vol_synth) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_vol_synth, sbmix->me_out_accu) < 0)
		goto __error;
	if ((sbmix->me_sw3_synth_input = snd_mixer_lib_sw3(mixer, "Synth Input Switch", 0, SND_MIXER_SWITCH3_FULL_FEATURED, 2, stereo_voices, snd_sb16mixer_midi_input_switch, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, sbmix->me_sw3_synth_input) < 0)
		goto __error;		
	if (snd_mixer_element_route_add(mixer, sbmix->me_vol_synth, sbmix->me_sw3_synth_input) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_sw3_synth_input, sbmix->me_in_accu) < 0)
		goto __error;
	/* CD */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_CD, 0, SND_MIXER_OSS_CD, snd_sb16mixer_group_cd, sbmix)) == NULL)
		goto __error;
 	if ((sbmix->me_in_cd = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_CD, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((sbmix->me_vol_cd = snd_mixer_lib_volume1(mixer, "CD Volume", 0, 2, db_range1, snd_sb16mixer_cd_volume_level, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, sbmix->me_vol_cd) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_in_cd, sbmix->me_vol_cd) < 0)
		goto __error;
	if ((sbmix->me_sw1_cd_output = snd_mixer_lib_sw1(mixer, "CD Output Switch", 0, 2, snd_sb16mixer_cd_output_switch, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, sbmix->me_sw1_cd_output) < 0)
		goto __error;		
	if (snd_mixer_element_route_add(mixer, sbmix->me_vol_cd, sbmix->me_sw1_cd_output) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_sw1_cd_output, sbmix->me_out_accu) < 0)
		goto __error;
	if ((sbmix->me_sw3_cd_input = snd_mixer_lib_sw3(mixer, "CD Input Switch", 0, SND_MIXER_SWITCH3_FULL_FEATURED, 2, stereo_voices, snd_sb16mixer_cd_input_switch, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, sbmix->me_sw3_cd_input) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_vol_cd, sbmix->me_sw3_cd_input) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_sw3_cd_input, sbmix->me_in_accu) < 0)
		goto __error;
	/* Line */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_LINE, 0, SND_MIXER_OSS_LINE, snd_sb16mixer_group_line, sbmix)) == NULL)
		goto __error;
 	if ((sbmix->me_in_line = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_LINE, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((sbmix->me_vol_line = snd_mixer_lib_volume1(mixer, "Line Volume", 0, 2, db_range1, snd_sb16mixer_line_volume_level, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, sbmix->me_vol_line) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_in_line, sbmix->me_vol_line) < 0)
		goto __error;
	if ((sbmix->me_sw1_line_output = snd_mixer_lib_sw1(mixer, "Line Output Switch", 0, 2, snd_sb16mixer_line_output_switch, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, sbmix->me_sw1_line_output) < 0)
		goto __error;		
	if (snd_mixer_element_route_add(mixer, sbmix->me_vol_line, sbmix->me_sw1_line_output) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_sw1_line_output, sbmix->me_out_accu) < 0)
		goto __error;
	if ((sbmix->me_sw3_line_input = snd_mixer_lib_sw3(mixer, "Line Input Switch", 0, SND_MIXER_SWITCH3_FULL_FEATURED, 2, stereo_voices, snd_sb16mixer_line_input_switch, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, sbmix->me_sw3_line_input) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_vol_line, sbmix->me_sw3_line_input) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_sw3_line_input, sbmix->me_in_accu) < 0)
		goto __error;
	/* MIC */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_OSS_MIC, snd_sb16mixer_group_mic, sbmix)) == NULL)
		goto __error;
 	if ((sbmix->me_in_mic = snd_mixer_lib_io_mono(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((sbmix->me_vol_mic = snd_mixer_lib_volume1(mixer, "MIC Volume", 0, 1, db_range1, snd_sb16mixer_mic_volume_level, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, sbmix->me_vol_mic) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_in_mic, sbmix->me_vol_mic) < 0)
		goto __error;
	if ((sbmix->me_sw1_mic_output = snd_mixer_lib_sw1(mixer, "MIC Output Switch", 0, 1, snd_sb16mixer_mic_output_switch, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, sbmix->me_sw1_mic_output) < 0)
		goto __error;		
	if (snd_mixer_element_route_add(mixer, sbmix->me_vol_mic, sbmix->me_sw1_mic_output) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_sw1_mic_output, sbmix->me_out_accu) < 0)
		goto __error;
	if ((sbmix->me_sw1_mic_input = snd_mixer_lib_sw1(mixer, "MIC Input Switch", 0, 2, snd_sb16mixer_mic_input_switch, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, sbmix->me_sw1_mic_input) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_vol_mic, sbmix->me_sw1_mic_input) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_sw1_mic_input, sbmix->me_in_accu) < 0)
		goto __error;
	/* Speaker */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_SPEAKER, 0, SND_MIXER_OSS_SPEAKER, snd_sb16mixer_group_speaker, sbmix)) == NULL)
		goto __error;
 	if ((sbmix->me_in_speaker = snd_mixer_lib_io_mono(mixer, SND_MIXER_IN_SPEAKER, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((sbmix->me_vol_speaker = snd_mixer_lib_volume1(mixer, "PC Speaker Volume", 0, 1, db_range2, snd_sb16mixer_speaker_volume_level, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, sbmix->me_vol_speaker) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_in_speaker, sbmix->me_vol_speaker) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_vol_speaker, sbmix->me_out_accu) < 0)
		goto __error;
	/* Capture endpoint */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_GRP_IGAIN, 0, SND_MIXER_OSS_IGAIN, snd_sb16mixer_group_igain, sbmix)) == NULL)
		goto __error;
	if ((sbmix->me_vol_igain = snd_mixer_lib_volume1(mixer, SND_MIXER_GRP_IGAIN, 0, 2, db_range3, snd_sb16mixer_igain_volume_level, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, sbmix->me_vol_igain) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_in_accu, sbmix->me_vol_igain) < 0)
		goto __error;
 	if ((sbmix->me_capture = snd_mixer_lib_pcm(mixer, SND_MIXER_ELEMENT_CAPTURE, 0, SND_MIXER_ETYPE_CAPTURE, 1, &pcm_dev)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, sbmix->me_vol_igain, sbmix->me_capture) < 0)
		goto __error;

	snd_mixer_switch_new(mixer, &snd_sb16mixer_mic_agc_switch, sbmix); /* MIC Automatic Gain Control */
	/* FIXME: 3d Stereo Enhancement only for SB DSP version > 4.13 !! */
	snd_mixer_switch_new(mixer, &snd_sb16mixer_3d_stereo_enh_switch, sbmix); /* 3d Stereo Enhancement */
	return mixer;

      __error:
      	snd_mixer_free(mixer);
      	return NULL;
}
