/*
 *  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 "driver.h"
#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(int w_flag,
				     unsigned int *prsw,
				     sbmixer_t * sbmix,
				     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;
	snd_spin_lock(sbmix, mixer, &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);
	}
	snd_spin_unlock(sbmix, mixer, &flags);
	return change;
}

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

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

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

static int snd_sb16mixer_mic_input_switch(int w_flag,
					  unsigned int *bitmap,
					  sbmixer_t * sbmix)
{
	unsigned long flags;
	unsigned char lreg, rreg, oleft, oright;
	int change = 0;
	
	snd_spin_lock(sbmix, mixer, &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);
	}
	snd_spin_unlock(sbmix, mixer, &flags);
	return change;
}

static int snd_sb16mixer_mic_output_switch(int w_flag,
					  unsigned int *bitmap,
					  sbmixer_t * sbmix)
{
	unsigned long flags;
	unsigned char old, oreg;
	int change = 0;
	
	snd_spin_lock(sbmix, mixer, &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);
	}
	snd_spin_unlock(sbmix, mixer, &flags);
	return change;
}

static int snd_sb16mixer_cd_output_switch(int w_flag,
					  unsigned int *bitmap,
					  sbmixer_t * sbmix)
{
	unsigned long flags;
	unsigned char oreg, oleft, oright;
	int change = 0;
	
	snd_spin_lock(sbmix, mixer, &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);
	}
	snd_spin_unlock(sbmix, mixer, &flags);
	return change;
}

static int snd_sb16mixer_line_output_switch(int w_flag,
					    unsigned int *bitmap,
					    sbmixer_t * sbmix)
{
	unsigned long flags;
	unsigned char oreg, oleft, oright;
	int change = 0;
	
	snd_spin_lock(sbmix, mixer, &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);
	}
	snd_spin_unlock(sbmix, mixer, &flags);
	return change;
}

static int snd_sb16mixer_volume_level(int w_flag, int *voices,
				      sbmixer_t *sbmix,
				      unsigned char max, 
				      unsigned char shift,
				      unsigned char reg)
{
	unsigned long flags;
	unsigned char lreg, rreg, oleft, oright;
	int change = 0;
	
	snd_spin_lock(sbmix, mixer, &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);
	}
	snd_spin_unlock(sbmix, mixer, &flags);
	return change;
	
}

static int snd_sb16mixer_mono_volume_level(int w_flag, int *voices,
					   sbmixer_t *sbmix,
					   unsigned char max,
					   unsigned char shift,
					   unsigned char reg)
{
	unsigned long flags;
	unsigned char oreg, oval;
	int change = 0;
	
	snd_spin_lock(sbmix, mixer, &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);
	}
	snd_spin_unlock(sbmix, mixer, &flags);
	return change;
	
}

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

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

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

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

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

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

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

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

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

static int snd_sb16mixer_tone_control(int w_flag,
			struct snd_mixer_element_tone_control1 *tc1,
			sbmixer_t *sbmix)
{
	unsigned long flags;
	unsigned char obass, otreble;
	int change = 0;
	
	snd_spin_lock(sbmix, mixer, &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 &= 0xf0;
			obass |= (tc1->bass & 15) << 4;
			snd_sb16mixer_write(sbmix, SB_DSP4_BASS_DEV, obass);
		}
		if (tc1->tc & SND_MIXER_TC1_TREBLE) {
			change |= ((otreble >> 4) & 15) != tc1->treble;
			otreble &= 0xf0;
			otreble |= (tc1->treble & 15) << 4;
			snd_sb16mixer_write(sbmix, SB_DSP4_TREBLE_DEV, otreble);
		}
	} else {
		tc1->tc = SND_MIXER_TC1_BASS | SND_MIXER_TC1_TREBLE;
		tc1->bass = (obass >> 4) & 15;
		tc1->treble = (otreble >> 4) & 15;
	}
	snd_spin_unlock(sbmix, mixer, &flags);
	return change;
}

static int snd_sb16mixer_get_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;
	snd_spin_lock(sbmix, mixer, &flags);
	uswitch->value.enable =
	    snd_sb16mixer_read(sbmix, SB_DSP4_MIC_AGC) & 0x01 ? 1 : 0;
	snd_spin_unlock(sbmix, mixer, &flags);
	return 0;
}

static int snd_sb16mixer_set_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;
	snd_spin_lock(sbmix, mixer, &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);
	snd_spin_unlock(sbmix, mixer, &flags);
	return change;
}

snd_kswitch_t snd_sb16mixer_switch =
{
	"Auto Gain Control",
	(snd_get_switch_t *)snd_sb16mixer_get_switch,
	(snd_set_switch_t *)snd_sb16mixer_set_switch,
	0,
	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;
	snd_kmixer_element_t *in_accu, *out_accu;
	snd_kmixer_element_t *element1, *element2, *element3, *element4;
	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);

	snd_spin_lock(sbmix, mixer, &flags);
	snd_sb16mixer_write(sbmix, 0x00, 0x00);	/* mixer reset */
	snd_spin_unlock(sbmix, mixer, &flags);

	/* build input and output accumulator */
	if ((in_accu = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_INPUT_ACCU, 0, 0)) == NULL)
		goto __error;
	if ((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(mixer, SND_MIXER_OUT_MASTER, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_volume1(mixer, "Master Volume", 0, 2, db_range1, (snd_mixer_volume1_control_t *)snd_sb16mixer_master_volume_level, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element1) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, out_accu, element1) < 0)
		goto __error;
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_GRP_TONE_CONTROL, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_tone_control1(mixer, SND_MIXER_GRP_TONE_CONTROL, 0, &tone_control, (snd_mixer_tone_control1_control_t *)snd_sb16mixer_tone_control, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, element2) < 0)
		goto __error;
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_GRP_OGAIN, 0)) == NULL)
		goto __error;
	if ((element3 = snd_mixer_lib_volume1(mixer, SND_MIXER_GRP_OGAIN, 0, 2, db_range3, (snd_mixer_volume1_control_t *)snd_sb16mixer_ogain_volume_level, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element3) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, element3) < 0)
		goto __error;
 	if ((element4 = 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, element3, element4) < 0)
		goto __error;
	/* PCM */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_PCM, 0)) == NULL)
		goto __error;
 	if ((element1 = snd_mixer_lib_pcm(mixer, SND_MIXER_ELEMENT_PLAYBACK, 0, SND_MIXER_ETYPE_PLAYBACK, 1, &pcm_dev)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "PCM Volume", 0, 2, db_range1, (snd_mixer_volume1_control_t *)snd_sb16mixer_pcm_volume_level, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, out_accu) < 0)
		goto __error;
	/* Synth */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_SYNTHESIZER, 0)) == NULL)
		goto __error;
 	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_SYNTHESIZER, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "Synth Volume", 0, 2, db_range1, (snd_mixer_volume1_control_t *)snd_sb16mixer_synth_volume_level, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, out_accu) < 0)
		goto __error;
	if ((element3 = snd_mixer_lib_sw3(mixer, "Synth Input Switch", 0, SND_MIXER_SWITCH3_FULL_FEATURED, 2, stereo_voices, (snd_mixer_sw3_control_t *)snd_sb16mixer_midi_input_switch, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element3) < 0)
		goto __error;		
	if (snd_mixer_element_route_add(mixer, element2, element3) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element3, in_accu) < 0)
		goto __error;
	/* CD */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_CD, 0)) == NULL)
		goto __error;
 	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_CD, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "CD Volume", 0, 2, db_range1, (snd_mixer_volume1_control_t *)snd_sb16mixer_cd_volume_level, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, element2) < 0)
		goto __error;
	if ((element3 = snd_mixer_lib_sw1(mixer, "CD Output Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_sb16mixer_cd_output_switch, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element3) < 0)
		goto __error;		
	if (snd_mixer_element_route_add(mixer, element2, element3) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element3, out_accu) < 0)
		goto __error;
	if ((element3 = snd_mixer_lib_sw3(mixer, "CD Input Switch", 0, SND_MIXER_SWITCH3_FULL_FEATURED, 2, stereo_voices, (snd_mixer_sw3_control_t *)snd_sb16mixer_cd_input_switch, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element3) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, element3) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element3, in_accu) < 0)
		goto __error;
	/* Line */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_LINE, 0)) == NULL)
		goto __error;
 	if ((element1 = snd_mixer_lib_io_stereo(mixer, SND_MIXER_IN_LINE, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "Line Volume", 0, 2, db_range1, (snd_mixer_volume1_control_t *)snd_sb16mixer_line_volume_level, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, element2) < 0)
		goto __error;
	if ((element3 = snd_mixer_lib_sw1(mixer, "Line Output Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_sb16mixer_line_output_switch, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element3) < 0)
		goto __error;		
	if (snd_mixer_element_route_add(mixer, element2, element3) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element3, out_accu) < 0)
		goto __error;
	if ((element3 = snd_mixer_lib_sw3(mixer, "Line Input Switch", 0, SND_MIXER_SWITCH3_FULL_FEATURED, 2, stereo_voices, (snd_mixer_sw3_control_t *)snd_sb16mixer_line_input_switch, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element3) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, element3) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element3, in_accu) < 0)
		goto __error;
	/* MIC */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_MIC, 0)) == NULL)
		goto __error;
 	if ((element1 = snd_mixer_lib_io_mono(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "MIC Volume", 0, 1, db_range1, (snd_mixer_volume1_control_t *)snd_sb16mixer_mic_volume_level, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, element2) < 0)
		goto __error;
	if ((element3 = snd_mixer_lib_sw1(mixer, "MIC Output Switch", 0, 1, (snd_mixer_sw1_control_t *)snd_sb16mixer_mic_output_switch, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element3) < 0)
		goto __error;		
	if (snd_mixer_element_route_add(mixer, element2, element3) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element3, out_accu) < 0)
		goto __error;
	if ((element3 = snd_mixer_lib_sw1(mixer, "MIC Input Switch", 0, 2, (snd_mixer_sw1_control_t *)snd_sb16mixer_mic_input_switch, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element3) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, element3) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element3, in_accu) < 0)
		goto __error;
	/* Speaker */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_IN_SPEAKER, 0)) == NULL)
		goto __error;
 	if ((element1 = snd_mixer_lib_io_mono(mixer, SND_MIXER_IN_SPEAKER, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "PC Speaker Volume", 0, 1, db_range2, (snd_mixer_volume1_control_t *)snd_sb16mixer_speaker_volume_level, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element2, out_accu) < 0)
		goto __error;
	/* capture endpoint */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_GRP_IGAIN, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_volume1(mixer, SND_MIXER_GRP_IGAIN, 0, 2, db_range3, (snd_mixer_volume1_control_t *)snd_sb16mixer_igain_volume_level, sbmix)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element1) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, in_accu, element1) < 0)
		goto __error;
 	if ((element2 = 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, element1, element2) < 0)
		goto __error;

	snd_mixer_switch_new(mixer, &snd_sb16mixer_switch, sbmix);
	return mixer;

      __error:
      	snd_mixer_free(mixer);
      	return NULL;
}
