/*
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *  Universal interface for Audio Codec '97
 *
 *  For more details look to AC '97 component specification revision 2.1
 *  by Intel Corporation (http://developer.intel.com).
 *
 *
 *   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__
#define SND_MAIN_OBJECT_FILE
#include "../../include/driver.h"
#include "../../include/ac97_codec.h"

/*

 */

static void snd_ac97_proc_init(snd_card_t * card, ac97_t * ac97);
static void snd_ac97_proc_done(ac97_t * ac97);

static struct {
	unsigned int id;
	char *name;
} snd_ac97_codec_ids[] = {
	{	0x414B4D00,	"Asahi Kasei AK4540"	},
	{	0x43525900,	"Cirrus Logic CS4297"   },
	{	0x83847600,	"SigmaTel STAC????"	},
	{	0x83847604,	"SigmaTel STAC9701/3/4/5" },
	{	0x83847605,	"SigmaTel STAC9704"	},
	{	0x83847608,	"SigmaTel STAC9708"	},
	{	0x83847609,	"SigmaTel STAC9721/23"	},
	{	0, 		NULL			}
};

static char *snd_ac97_stereo_enhancements[] =
{
  /*   0 */ "No 3D Stereo Enhancement",
  /*   1 */ "Analog Devices Phat Stereo",
  /*   2 */ "Creative Stereo Enhancement",
  /*   3 */ "National Semi 3D Stereo Enhancement",
  /*   4 */ "YAMAHA Ymersion",
  /*   5 */ "BBE 3D Stereo Enhancement",
  /*   6 */ "Crystal Semi 3D Stereo Enhancement",
  /*   7 */ "Qsound QXpander",
  /*   8 */ "Spatializer 3D Stereo Enhancement",
  /*   9 */ "SRS 3D Stereo Enhancement",
  /*  10 */ "Platform Tech 3D Stereo Enhancement",
  /*  11 */ "AKM 3D Audio",
  /*  12 */ "Aureal Stereo Enhancement",
  /*  13 */ "Aztech 3D Enhancement",
  /*  14 */ "Binaura 3D Audio Enhancement",
  /*  15 */ "ESS Technology Stereo Enhancement",
  /*  16 */ "Harman International VMAx",
  /*  17 */ "Nvidea 3D Stereo Enhancement",
  /*  18 */ "Philips Incredible Sound",
  /*  19 */ "Texas Instruments 3D Stereo Enhancement",
  /*  20 */ "VLSI Technology 3D Stereo Enhancement",
  /*  21 */ "TriTech 3D Stereo Enhancement",
  /*  22 */ "Realtek 3D Stereo Enhancement",
  /*  23 */ "Samsung 3D Stereo Enhancement",
  /*  24 */ "Wolfson Microelectronics 3D Enhancement",
  /*  25 */ "Delta Integration 3D Enhancement",
  /*  26 */ "SigmaTel 3D Enhancement",
  /*  27 */ "Reserved 27",
  /*  28 */ "Rockwell 3D Stereo Enhancement",
  /*  29 */ "Reserved 29",
  /*  30 */ "Reserved 30",
  /*  31 */ "Reserved 31"
};

/*

 */

static int snd_ac97_get_recsrc(ac97_t *ac97, snd_kmixer_element_t *element)
{
	if (element == ac97->me_mux_mic) {  
		return 0;
	} else if (element == ac97->me_mux_cd) {
		return 1;
	} else if (element == ac97->me_mux_video) {
		return 2;
	} else if (element == ac97->me_mux_aux) {
		return 3;
	} else if (element == ac97->me_mux_line) {
		return 4;
	} else if (element == ac97->me_mux_mix) {
		return 5;
	} else if (element == ac97->me_mux_mono_mix) {
		return 6;
	} else if (element == ac97->me_mux_phone) {
		return 7;
	}
	return 0;
}

static snd_kmixer_element_t *snd_ac97_get_recsrc_element(ac97_t *ac97, int src)
{
	switch (src) {
	case 0:
	default: 
		return ac97->me_mux_mic;
	case 1:
		return ac97->me_mux_cd;
	case 2:
		return ac97->me_mux_video;
	case 3:
		return ac97->me_mux_aux;
	case 4:
		return ac97->me_mux_line;
	case 5:
		return ac97->me_mux_mix;
	case 6:
		return ac97->me_mux_mono_mix;
	case 7:
		return ac97->me_mux_phone;
	}
}

static int snd_ac97_mux(void *private_data, int w_flag, snd_kmixer_element_t **elements)
{
	ac97_t *ac97 = (ac97_t *) private_data;
	unsigned long flags;
	int change = 0;
	snd_kmixer_element_t *left, *right;
	int nleft, nright;

	spin_lock_irqsave(&ac97->reg_lock, flags);
	left = snd_ac97_get_recsrc_element(ac97, (ac97->regs[AC97_REC_SEL] >> 8) & 7);
	right = snd_ac97_get_recsrc_element(ac97, ac97->regs[AC97_REC_SEL] & 7);
	if (!w_flag) {
		elements[0] = left;
		elements[1] = right;
	} else {
		change = elements[0] != left || elements[1] != right;
		nleft = snd_ac97_get_recsrc(ac97, elements[0]);
		nright = snd_ac97_get_recsrc(ac97, elements[1]);
		ac97->write(ac97->private_data, AC97_REC_SEL,
			(ac97->regs[AC97_REC_SEL] = (nleft << 8) | nright));
	}
	spin_unlock_irqrestore(&ac97->reg_lock, flags);
	return change;
}

static int snd_ac97_mono_out_mux(void *private_data, int w_flag, snd_kmixer_element_t **element)
{
	ac97_t *ac97 = (ac97_t *)private_data;
	unsigned long flags;
	int change = 0;
	snd_kmixer_element_t *val;

	spin_lock_irqsave(&ac97->reg_lock, flags);
	val = ac97->regs[AC97_GENERAL_PURPOSE] & 0x0200 ?
			ac97->me_mux2_mic :
			ac97->me_mux2_out_mono_accu;
	if (!w_flag) {
		*element = val;
	} else {
		change = *element != val;
		ac97->regs[AC97_GENERAL_PURPOSE] &= ~0x0200;
		if (*element == ac97->me_mux2_mic)
			ac97->regs[AC97_GENERAL_PURPOSE] |= 0x0200;
		ac97->write(ac97->private_data, AC97_GENERAL_PURPOSE, ac97->regs[AC97_GENERAL_PURPOSE]);
	}
	spin_unlock_irqrestore(&ac97->reg_lock, flags);
	return change;
}

static int snd_ac97_mute(ac97_t *ac97, int w_flag, int *value, unsigned char reg)
{
	unsigned long flags;
	unsigned short val;
	int change = 0;
	
	spin_lock_irqsave(&ac97->reg_lock, flags);
	val = ((ac97->regs[reg] >> 15) & 1) ^ 1;
	if (!w_flag) {
		*value = val;
	} else {
		change = val != *value;
		val = (ac97->regs[reg] & 0x7fff) | (*value ? 0 : 0x8000);
		ac97->write(ac97->private_data, reg, ac97->regs[reg] = val);
	}
	spin_unlock_irqrestore(&ac97->reg_lock, flags);
	return change;
}

#define AC97_MUTE(name, reg) \
	static int snd_ac97_mute_##name(void *private_data, int w_flag, int *value) \
	{ \
		return snd_ac97_mute((ac97_t *)private_data, w_flag, value, reg); \
	}

AC97_MUTE(master, AC97_MASTER)
AC97_MUTE(headphone, AC97_HEADPHONE)
AC97_MUTE(master_mono, AC97_MASTER_MONO)
AC97_MUTE(pc_beep, AC97_PC_BEEP)
AC97_MUTE(phone, AC97_PHONE)
AC97_MUTE(mic, AC97_MIC)
AC97_MUTE(line, AC97_LINE)
AC97_MUTE(cd, AC97_CD)
AC97_MUTE(video, AC97_VIDEO)
AC97_MUTE(aux, AC97_AUX)
AC97_MUTE(pcm, AC97_PCM)
AC97_MUTE(record_gain, AC97_REC_GAIN)
AC97_MUTE(record_gain_mic, AC97_REC_GAIN_MIC)
     
static int snd_ac97_mute_center(void *private_data, int w_flag, int *value)
{
	ac97_t *ac97 = (ac97_t *)private_data;
	unsigned long flags;
	unsigned short val;
	int change = 0;

	spin_lock_irqsave(&ac97->reg_lock, flags);
	val = ((ac97->regs[AC97_CENTER_LFE_MASTER] >> 7) & 1) ^ 1;
	if (!w_flag) {
		*value = val;
	} else {
		change = val != *value;
		ac97->regs[AC97_CENTER_LFE_MASTER] &= 0xff7f;
		if (!snd_mixer_get_bit(value, 0))
			ac97->regs[AC97_CENTER_LFE_MASTER] |= 0x0080;
		ac97->write(ac97->private_data, AC97_CENTER_LFE_MASTER, ac97->regs[AC97_CENTER_LFE_MASTER]);
	}
	spin_unlock_irqrestore(&ac97->reg_lock, flags);
	return change;
}

static int snd_ac97_mute_lfe(void *private_data, int w_flag, int *value)
{
	ac97_t *ac97 = (ac97_t *)private_data;
	unsigned long flags;
	unsigned short val;
	int change = 0;

	spin_lock_irqsave(&ac97->reg_lock, flags);
	val = ((ac97->regs[AC97_CENTER_LFE_MASTER] >> 15) & 1) ^ 1;
	if (!w_flag) {
		*value = val;
	} else {
		change = val != *value;
		ac97->regs[AC97_CENTER_LFE_MASTER] &= 0x7fff;
		if (!snd_mixer_get_bit(value, 0))
			ac97->regs[AC97_CENTER_LFE_MASTER] |= 0x8000;
		ac97->write(ac97->private_data, AC97_CENTER_LFE_MASTER, ac97->regs[AC97_CENTER_LFE_MASTER]);
	}
	spin_unlock_irqrestore(&ac97->reg_lock, flags);
	return change;
}

static int snd_ac97_mute_surround(void *private_data, int w_flag, unsigned int *value)
{
	ac97_t *ac97 = (ac97_t *)private_data;
	unsigned long flags;
	unsigned short left, right;
	int change = 0;

	spin_lock_irqsave(&ac97->reg_lock, flags);
	left = ((ac97->regs[AC97_SURROUND_MASTER] >> 15) & 1) ^ 1;
	right = ((ac97->regs[AC97_SURROUND_MASTER] >> 7) & 1) ^ 1;
	if (!w_flag) {
		snd_mixer_set_bit(value, 0, left);
		snd_mixer_set_bit(value, 1, right);
	} else {
		change = left != snd_mixer_get_bit(value, 0) ||
			 right != snd_mixer_get_bit(value, 1);
		ac97->regs[AC97_SURROUND_MASTER] &= 0x7fff;
		if (!snd_mixer_get_bit(value, 0))
			ac97->regs[AC97_SURROUND_MASTER] |= 0x8000;
		ac97->regs[AC97_SURROUND_MASTER] &= 0xff7f;
		if (!snd_mixer_get_bit(value, 1))
			ac97->regs[AC97_SURROUND_MASTER] |= 0x0080;
		ac97->write(ac97->private_data, AC97_SURROUND_MASTER, ac97->regs[AC97_SURROUND_MASTER]);
	}
	spin_unlock_irqrestore(&ac97->reg_lock, flags);
	return change;
}

static int snd_ac97_tone_control(void *private_data, int w_flag,
				 struct snd_mixer_element_tone_control1 *tc1)
{
	ac97_t *ac97 = (ac97_t *)private_data;
	unsigned long flags;
	unsigned char bass, treble;
	int change = 0;
	
	spin_lock_irqsave(&ac97->reg_lock, flags);
	bass = (ac97->regs[AC97_MASTER_TONE] >> 8) & 15;
	treble = ac97->regs[AC97_MASTER_TONE] & 15;
	if (!w_flag) {
		tc1->sw = bass != 15;
		tc1->bass = ac97->bass;
		tc1->treble = ac97->treble;
	} else {
		change = 0;
		if (tc1->tc & SND_MIXER_TC1_SW) {
			change |= (bass != 15 ? 1 : 0) != tc1->sw;
			if (tc1->sw) {
				ac97->regs[AC97_MASTER_TONE] |= (bass = ((14 - ac97->bass) << 8)) | (treble = (14 - ac97->treble));
			} else {
				ac97->regs[AC97_MASTER_TONE] |= (15 << 8) | 15;
				bass = treble = 15;
			}
		}
		if (tc1->tc & SND_MIXER_TC1_BASS) {
			change |= ac97->bass != tc1->bass;
			if (bass != 15) {
				ac97->regs[AC97_MASTER_TONE] &= ~(15 << 8);
				ac97->regs[AC97_MASTER_TONE] |= (14 - tc1->bass) << 8;
			}
			ac97->bass = tc1->bass;
		}
		if (tc1->tc & SND_MIXER_TC1_TREBLE) {
			change |= ac97->treble != tc1->treble;
			if (treble != 15) {
				ac97->regs[AC97_MASTER_TONE] &= ~15;
				ac97->regs[AC97_MASTER_TONE] |= (14 - tc1->treble);
			}
			ac97->treble = treble;
		}
		ac97->write(ac97->private_data, AC97_MASTER_TONE, ac97->regs[AC97_MASTER_TONE]);
	}
	spin_unlock_irqrestore(&ac97->reg_lock, flags);
	return change;	
}

static int snd_ac97_volume_stereo(ac97_t *ac97, int w_flag, int *volume, int reg, int max)
{
	unsigned long flags;
	unsigned char left, right;
	int change = 0;
	
	spin_lock_irqsave(&ac97->reg_lock, flags);
	left = max - ((ac97->regs[reg] >> 8) & max);
	right = max - (ac97->regs[reg] & max);
	if (!w_flag) {
		volume[0] = left;
		volume[1] = right;
	} else {
		change = volume[0] != left || volume[1] != right;
		ac97->regs[reg] &= ~((max << 8) | max);
		ac97->regs[reg] |= ((max - volume[0]) << 8) | (max - volume[1]);
		ac97->write(ac97->private_data, reg, ac97->regs[reg]);
	}
	spin_unlock_irqrestore(&ac97->reg_lock, flags);
	return change;
}

static int snd_ac97_volume_mono(ac97_t *ac97, int w_flag, int *volume, int reg, int max)
{
	unsigned long flags;
	unsigned char val;
	int change = 0;
	
	spin_lock_irqsave(&ac97->reg_lock, flags);
	val = max - (ac97->regs[reg] & max);
	if (!w_flag) {
		volume[0] = val;
	} else {
		change = volume[0] != val;
		ac97->regs[reg] &= ~max;
		ac97->regs[reg] |= max - volume[0];
		ac97->write(ac97->private_data, reg, ac97->regs[reg]);
	}
	spin_unlock_irqrestore(&ac97->reg_lock, flags);
	return change;
}

#define AC97_VOLUME(name, which, reg, max) \
	static int snd_ac97_volume_##name(void *private_data, int w_flag, int *volume) \
        { \
		return snd_ac97_volume_##which((ac97_t *)private_data, \
					       w_flag, volume, reg, (max)); \
	}

AC97_VOLUME(master, stereo, AC97_MASTER, ((ac97_t *)private_data)->max_master)
AC97_VOLUME(headphone, stereo, AC97_HEADPHONE, ((ac97_t *)private_data)->max_headphone)
AC97_VOLUME(master_mono, mono, AC97_MASTER_MONO, ((ac97_t *)private_data)->max_master_mono)
AC97_VOLUME(phone, mono, AC97_PHONE, 31)
AC97_VOLUME(mic, mono, AC97_MIC, 31)
AC97_VOLUME(line, stereo, AC97_LINE, 31)
AC97_VOLUME(cd, stereo, AC97_CD, 31)
AC97_VOLUME(video, stereo, AC97_VIDEO, 31)
AC97_VOLUME(aux, stereo, AC97_AUX, 31)
AC97_VOLUME(pcm, stereo, AC97_PCM, 31)
AC97_VOLUME(record_gain, stereo, AC97_REC_GAIN, 31)
AC97_VOLUME(record_gain_mic, mono, AC97_REC_GAIN_MIC, 31)
AC97_VOLUME(center, mono, AC97_CENTER_LFE_MASTER, ((ac97_t *)private_data)->max_center)
AC97_VOLUME(surround, stereo, AC97_SURROUND_MASTER, ((ac97_t *)private_data)->max_surround)

static int snd_ac97_volume_pc_beep(void *private_data, int w_flag, int *volume)
{
	ac97_t *ac97 = (ac97_t *)private_data;
	unsigned long flags;
	unsigned char val;
	int change = 0;
	
	spin_lock_irqsave(&ac97->reg_lock, flags);
	val = (ac97->regs[AC97_PC_BEEP] >> 1) & 15;
	if (!w_flag) {
		volume[0] = val;
	} else {
		change = volume[0] != val;
		ac97->regs[AC97_PC_BEEP] &= ~(15 << 1);
		ac97->regs[AC97_PC_BEEP] |= volume[0] << 1;
		ac97->write(ac97->private_data, AC97_PC_BEEP, ac97->regs[AC97_PC_BEEP]);
	}
	spin_unlock_irqrestore(&ac97->reg_lock, flags);
	return change;
}

static int snd_ac97_mic_boost_volume(void *private_data, int w_flag, int *volume)
{
	ac97_t *ac97 = (ac97_t *)private_data;
	unsigned long flags;
	unsigned char val;
	int change = 0;
	
	spin_lock_irqsave(&ac97->reg_lock, flags);
	val = (ac97->regs[AC97_MIC] >> 6) & 1;
	if (!w_flag) {
		volume[0] = val;
	} else {
		change = volume[0] != val;
		ac97->regs[AC97_MIC] &= ~(1 << 6);
		ac97->regs[AC97_MIC] |= volume[0] << 6;
		ac97->write(ac97->private_data, AC97_MIC, ac97->regs[AC97_MIC]);
	}
	spin_unlock_irqrestore(&ac97->reg_lock, flags);
	return change;
}

static int snd_ac97_volume_lfe(void *private_data, int w_flag, int *volume)
{
	ac97_t *ac97 = (ac97_t *)private_data;
	unsigned long flags;
	unsigned char val;
	int change = 0;
	
	spin_lock_irqsave(&ac97->reg_lock, flags);
	val = (ac97->regs[AC97_CENTER_LFE_MASTER] >> 8) & ac97->max_lfe;
	if (!w_flag) {
		volume[0] = val;
	} else {
		change = volume[0] != val;
		ac97->regs[AC97_CENTER_LFE_MASTER] &= ~(ac97->max_lfe << 8);
		ac97->regs[AC97_CENTER_LFE_MASTER] |= volume[0] << 8;
		ac97->write(ac97->private_data, AC97_CENTER_LFE_MASTER, ac97->regs[AC97_CENTER_LFE_MASTER]);
	}
	spin_unlock_irqrestore(&ac97->reg_lock, flags);
	return change;
}

static int snd_ac97_3d(void *private_data, int w_flag, struct snd_mixer_element_3d_effect1 *effect1)
{
	ac97_t *ac97 = (ac97_t *)private_data;
	unsigned long flags;
	unsigned char center = 0, depth, depth_rear = 0, sw;
	int change = 0;

	spin_lock_irqsave(&ac97->reg_lock, flags);
	sw = (ac97->regs[AC97_GENERAL_PURPOSE] & 0x2000) ? 1 : 0;
	if (ac97->id == 0x83847608) {	// Sigmatel 9708
		depth = ac97->regs[AC97_3D_CONTROL] & ac97->max_3d;
		depth_rear = (ac97->regs[AC97_3D_CONTROL] >> 2) & ac97->max_3d;
	} else {
		center = ((ac97->regs[AC97_3D_CONTROL] >> 8) >> ac97->shift_3d) & ac97->max_3d;
		depth = (ac97->regs[AC97_3D_CONTROL] >> ac97->shift_3d) & ac97->max_3d;
	}
	if (!w_flag) {
		effect1->sw = sw;
		if (ac97->id == 0x83847608) {	// Sigmatel 9708
			effect1->depth = depth;
			effect1->depth_rear = depth_rear;
		} else {
			effect1->center = center;
			effect1->depth = depth;
		}
	} else {
		if (ac97->id == 0x83847608) {	// Sigmatel 9708
			change = effect1->sw != sw ||
				  effect1->depth != depth ||
				   effect1->depth_rear != depth_rear;
				
		} else {
			change = effect1->sw != sw ||
		       		  effect1->center != center ||
		   	   	   effect1->depth != depth;
		}
		ac97->regs[AC97_GENERAL_PURPOSE] &= ~0x2000;
		if (effect1->sw)
			ac97->regs[AC97_GENERAL_PURPOSE] |= 0x2000;
		if (ac97->id == 0x83847608) {	// Sigmatel 9708
			ac97->regs[AC97_3D_CONTROL] &= ~((ac97->max_3d << 2) | ac97->max_3d);
			ac97->regs[AC97_3D_CONTROL] |=	(effect1->depth_rear << 2) | effect1->depth;
		} else {
			ac97->regs[AC97_3D_CONTROL] &= ~(((ac97->max_3d << 8) | ac97->max_3d) << ac97->shift_3d);
			ac97->regs[AC97_3D_CONTROL] |= ((effect1->center << 8) | effect1->depth) << ac97->shift_3d;
		}
		ac97->write(ac97->private_data, AC97_3D_CONTROL, ac97->regs[AC97_3D_CONTROL]);
		ac97->write(ac97->private_data, AC97_GENERAL_PURPOSE, ac97->regs[AC97_GENERAL_PURPOSE]);
	}
	spin_unlock_irqrestore(&ac97->reg_lock, flags);	
	return change;
}

/*
 *
 */

static int snd_ac97_get_switch(snd_kmixer_t * mixer,
                               snd_kswitch_t * kswitch,
                               snd_switch_t * uswitch)
{
	unsigned long flags;
	ac97_t *ac97 = (ac97_t *) kswitch->private_data;
	unsigned short val;

	uswitch->type = SND_SW_TYPE_BOOLEAN;
	spin_lock_irqsave(&ac97->reg_lock, flags);
	val = ac97->regs[AC97_GENERAL_PURPOSE];
	uswitch->value.enable = val & kswitch->private_value ? 1 : 0;
	spin_unlock_irqrestore(&ac97->reg_lock, flags);
	return 0;
}

static int snd_ac97_set_switch(snd_kmixer_t * mixer,
			       snd_kswitch_t * kswitch,
			       snd_switch_t * uswitch)
{
	unsigned long flags;
	ac97_t *ac97 = (ac97_t *) kswitch->private_data;
	unsigned short val, val1;
	int change = 0;

	if (uswitch->type != SND_SW_TYPE_BOOLEAN)
		return -EINVAL;
	val1 = uswitch->value.enable ? kswitch->private_value : 0x0000;
	spin_lock_irqsave(&ac97->reg_lock, flags);
	val = ac97->regs[AC97_GENERAL_PURPOSE];
	change = (val & kswitch->private_value) != val1 ? 1 : 0;
	val &= ~kswitch->private_value;
	val |= val1;
	ac97->regs[AC97_GENERAL_PURPOSE] = val;
	ac97->write(ac97->private_data, AC97_GENERAL_PURPOSE, val);
	spin_unlock_irqrestore(&ac97->reg_lock, flags);
	return change;
}

#define AC97_SWITCHES \
		(sizeof(snd_ac97_switches)/sizeof(snd_kswitch_t))

static snd_kswitch_t snd_ac97_switches[] =
{
	{
		"AC97 PCM Output Path",
		(snd_get_switch_t *)snd_ac97_get_switch,
		(snd_set_switch_t *)snd_ac97_set_switch,
		0x8000,
		NULL,
		NULL
	},
	{
		SND_MIXER_SW_SIM_STEREO,
		(snd_get_switch_t *)snd_ac97_get_switch,
		(snd_set_switch_t *)snd_ac97_set_switch,
		0x4000,
		NULL,
		NULL
	},
	{
		SND_MIXER_SW_LOUDNESS,
		(snd_get_switch_t *)snd_ac97_get_switch,
		(snd_set_switch_t *)snd_ac97_set_switch,
		0x1000,
		NULL,
		NULL
	},
	{
		"AC97 Mono Output Select",
		(snd_get_switch_t *)snd_ac97_get_switch,
		(snd_set_switch_t *)snd_ac97_set_switch,
		0x0200,
		NULL,
		NULL
	},
	{
		"AC97 Second MIC",
		(snd_get_switch_t *)snd_ac97_get_switch,
		(snd_set_switch_t *)snd_ac97_set_switch,
		0x0100,
		NULL,
		NULL
	},
	{
		"ADC/DAC Loopback",
		(snd_get_switch_t *)snd_ac97_get_switch,
		(snd_set_switch_t *)snd_ac97_set_switch,
		0x0080,
		NULL,
		NULL
	},
};

/*
 *
 */

static void snd_ac97_mixer_free(void *data)
{
	ac97_t *ac97 = (ac97_t *) data;

	if (ac97) {
		snd_ac97_proc_done(ac97);
		if (ac97->private_free)
			ac97->private_free(ac97);
	}
}

static int snd_ac97_try_volume_mix(ac97_t * ac97, int reg)
{
	unsigned short val;

	switch (reg) {
	case AC97_MASTER_TONE:
		return ac97->caps & 0x04 ? 1 : 0;
	case AC97_HEADPHONE:
		return ac97->caps & 0x10 ? 1 : 0;
	case AC97_REC_GAIN_MIC:
		return ac97->caps & 0x01 ? 1 : 0;
	case AC97_3D_CONTROL:
		if (ac97->caps & 0x7c00) {
			val = ac97->read(ac97->private_data, reg);
			/* if nonzero - fixed and we can't set it */
			return val == 0;
		}
		return 0;
	case AC97_CENTER_LFE_MASTER:	/* center */
		val = ac97->read(ac97->private_data, AC97_EXTENDED_STATUS);
		return val != 0xffff && (val & 0x40) ? 1 : 0;
	case AC97_CENTER_LFE_MASTER+1:	/* lfe */
		val = ac97->read(ac97->private_data, AC97_EXTENDED_STATUS);
		return val != 0xffff && (val & 0x100) ? 1 : 0;		
	case AC97_SURROUND_MASTER: /* read extended audio status control */
		val = ac97->read(ac97->private_data, AC97_EXTENDED_STATUS);
		return val != 0xffff && (val & 0x80) ? 1 : 0;
	}
	val = ac97->read(ac97->private_data, reg);
	if (!(val & 0x8000)) {
		/* nothing seems to be here - mute flag isn't set */
		/* try another test */
		ac97->write(ac97->private_data, reg, val | 0x8000);
		val = ac97->read(ac97->private_data, reg);
		if (!(val & 0x8000))
			return 0;	/* nothing here */
	}
	return 1;		/* success, useable */
}

static void snd_ac97_change_volume_params1(ac97_t * ac97, int reg, unsigned char *max)
{
	unsigned short val, val1;

	*max = 63;
	val = 0x8000 | 0x0020;
	ac97->write(ac97->private_data, reg, val);
	val1 = ac97->read(ac97->private_data, reg);
	if (val != val1) {
		*max = 31;
	}
	/* reset volume to zero */
	ac97->write(ac97->private_data, reg, 0x8000);
}

static void snd_ac97_change_volume_params2(ac97_t * ac97, int reg, unsigned char *max)
{
	unsigned short val, val1;

	*max = 63;
	val = 0x8000 | 0x2000;
	ac97->write(ac97->private_data, reg, val);
	val1 = ac97->read(ac97->private_data, reg);
	if (val != val1) {
		*max = 31;
	}
	/* reset volume to zero */
	ac97->write(ac97->private_data, reg, 0x8080);
}

static void snd_ac97_change_3d_params(ac97_t * ac97)
{
	unsigned short val;

	if (ac97->id == 0x83847608) {	// Sigmatel 9708
		ac97->max_3d = 3;
		ac97->shift_3d = 0;	// not used.
		ac97->write(ac97->private_data, AC97_3D_CONTROL, 0x000f);
	} else {
		ac97->max_3d = 15;
		ac97->shift_3d = 0;
		val = 0x0707;
		ac97->write(ac97->private_data, AC97_3D_CONTROL, val);
		val = ac97->read(ac97->private_data, AC97_3D_CONTROL);
		if (val == 0x0606) {
			ac97->max_3d = 7;
			ac97->shift_3d = 1;
		}
		ac97->write(ac97->private_data, AC97_3D_CONTROL, 0x0f0f);
	}
}

static int snd_ac97_build_mono_input(snd_kmixer_t *mixer,
				     ac97_t *ac97,
				     char *name,
				     snd_mixer_sw2_control_t *sw_control,
				     snd_kmixer_element_t **sw_element,
				     snd_mixer_volume1_control_t *vol_control,
				     snd_kmixer_element_t **vol_element,
				     struct snd_mixer_element_volume1_range *range,
				     snd_kmixer_element_t **mux,
				     int oss_dev,
				     snd_kmixer_group_control_t *gcontrol)
{
	snd_kmixer_group_t *group;
	snd_kmixer_element_t *element1, *element2, *element3;
	char str[80];
	static struct snd_mixer_element_volume1_range mic_range[1] = {
		{0, 1, 0, 2000}
	};

	if ((group = snd_mixer_lib_group_ctrl(mixer, name, 0, oss_dev, gcontrol, ac97)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_mono(mixer, name, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if (sw_control == snd_ac97_mute_mic) {
		if ((element2 = snd_mixer_lib_volume1(mixer, "MIC Boost", 0, 1, mic_range, snd_ac97_mic_boost_volume, ac97)) == NULL)
			goto __error;
		if (snd_mixer_element_route_add(mixer, element1, element2) < 0)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, element2) < 0)
			goto __error;
		element1 = element2;
	}
	if (mux) {
		if (snd_mixer_group_element_add(mixer, group, ac97->me_mux) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, element1, ac97->me_mux) < 0)
			goto __error;
		*mux = element1;
	}
	sprintf(str, "%s Volume", name);
	if ((element2 = snd_mixer_lib_volume1(mixer, str, 0, 1, range, vol_control, ac97)) == 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;
	sprintf(str, "%s Switch", name);
	if ((element3 = snd_mixer_lib_sw2(mixer, str, 0, sw_control, ac97)) == 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;
	*sw_element = element3;
	*vol_element = element2;
	return 0;

      __error:
      	return 1; 
}

static int snd_ac97_build_stereo_input(snd_kmixer_t *mixer,
				       ac97_t *ac97,
				       char *name,
				       snd_mixer_sw2_control_t *sw_control,
				       snd_kmixer_element_t **sw_element,
				       snd_mixer_volume1_control_t *vol_control,
				       snd_kmixer_element_t **vol_element,
				       snd_kmixer_element_t **mux,
				       int oss_dev,
				       snd_kmixer_group_control_t *gcontrol)
{
	snd_kmixer_group_t *group;
	snd_kmixer_element_t *element1, *element2, *element3;
	char str[80];
	static struct snd_mixer_element_volume1_range table13_range[2] = {
		{0, 31, -3450, 1200},
		{0, 31, -3450, 1200}
	};

	if ((group = snd_mixer_lib_group_ctrl(mixer, name, 0, oss_dev, gcontrol, ac97)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_stereo(mixer, name, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	if (mux) {
		if (snd_mixer_group_element_add(mixer, group, ac97->me_mux) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, element1, ac97->me_mux) < 0)
			goto __error;
		*mux = element1;
	}
	sprintf(str, "%s Volume", name);
	if ((element2 = snd_mixer_lib_volume1(mixer, str, 0, 2, table13_range, vol_control, ac97)) == 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;
	sprintf(str, "%s Switch", name);
	if ((element3 = snd_mixer_lib_sw2(mixer, str, 0, sw_control, ac97)) == 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;
	*sw_element = element3;
	*vol_element = element2;
	return 0;

      __error:
      	return 1;
}

static int snd_ac97_mixer_group_ctrl1(ac97_t *ac97,
				      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_sw2_control_t *sw2,
				      snd_kmixer_element_t *sw2_element,
				      snd_kmixer_element_t *mux_in)
{
	int voices[2];
	snd_kmixer_element_t *elements[2];
	int value;
	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(ac97, 0, voices);
			ugroup->volume.names.front_left = voices[0];
			ugroup->volume.names.front_right = voices[1];
			ugroup->min = 0;
			ugroup->max = max;
		}
		if (sw2) {
			ugroup->caps |= SND_MIXER_GRPCAP_MUTE | SND_MIXER_GRPCAP_JOINTLY_MUTE;
			sw2(ac97, 0, &value);
			ugroup->mute = 0;
			if (!value)
				ugroup->mute |= SND_MIXER_CHN_MASK_STEREO;
		}
		if (mux_in) {
			ugroup->caps |= SND_MIXER_GRPCAP_CAPTURE | SND_MIXER_GRPCAP_EXCL_CAPTURE;
			ugroup->capture_group = 1;
			snd_ac97_mux(ac97, 0, elements);
			ugroup->capture = 0;
			if (elements[0] == mux_in)
				ugroup->capture |= SND_MIXER_CHN_MASK_FRONT_LEFT;
			if (elements[1] == mux_in)
				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(ac97, 1, voices) > 0) {
				snd_mixer_element_value_change(file, volume1_element, 0);
				change = 1;
			}
		}
		if (sw2) {
			value = 0;
			if ((ugroup->mute & SND_MIXER_CHN_MASK_STEREO) == 0)
				value = 1;
			if (sw2(ac97, 1, &value) > 0) {
				snd_mixer_element_value_change(file, sw2_element, 0);
				change = 1;
			}
		}
		if (mux_in) {
			snd_ac97_mux(ac97, 0, elements);
			if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_LEFT)
				elements[0] = mux_in;
			if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_RIGHT)
				elements[1] = mux_in;
			if (snd_ac97_mux(ac97, 1, elements) > 0) {
				snd_mixer_element_value_change(file, ac97->me_mux, 0);
				change = 1;
			}
		}
	}
	return change;
}

#define AC97_GROUP_STEREO(name, volume1_element, max, sw2_element, mux_in) \
	static int snd_ac97_mixer_group_##name(void *private_data, \
					       snd_kmixer_file_t * file, \
					       int w_flag, \
					       snd_mixer_group_t * ugroup) \
	{ \
		ac97_t *ac97 = (ac97_t *)private_data; \
		return snd_ac97_mixer_group_ctrl1(private_data, file, \
						  w_flag, ugroup, \
						  snd_ac97_volume_##name, \
						  volume1_element, \
						  max, \
						  snd_ac97_mute_##name, \
						  sw2_element, \
						  mux_in); \
	}

static int snd_ac97_mixer_group_ctrl2(ac97_t *ac97,
				      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_sw2_control_t *sw2,
				      snd_kmixer_element_t *sw2_element,
				      snd_kmixer_element_t *mux_in)
{
	int voice;
	snd_kmixer_element_t *elements[2];
	int value;
	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(ac97, 0, &voice);
			ugroup->volume.names.front_left = voice;
			ugroup->volume.names.front_right = voice;
			ugroup->min = 0;
			ugroup->max = max;
		}
		if (sw2) {
			ugroup->caps |= SND_MIXER_GRPCAP_MUTE | SND_MIXER_GRPCAP_MUTE;
			sw2(ac97, 0, &value);
			ugroup->mute = 0;
			if (!value)
				ugroup->mute |= SND_MIXER_CHN_MASK_STEREO;
		}
		if (mux_in) {
			ugroup->caps |= SND_MIXER_GRPCAP_CAPTURE | SND_MIXER_GRPCAP_EXCL_CAPTURE;
			ugroup->capture_group = 1;
			snd_ac97_mux(ac97, 0, elements);
			ugroup->capture = 0;
			if (elements[0] == mux_in)
				ugroup->capture |= SND_MIXER_CHN_MASK_FRONT_LEFT;
			if (elements[1] == mux_in)
				ugroup->capture |= SND_MIXER_CHN_MASK_FRONT_RIGHT;
		}
	} else {
		if (volume1) {
			voice = ugroup->volume.names.front_left & max;
			if (volume1(ac97, 1, &voice) > 0) {
				snd_mixer_element_value_change(file, volume1_element, 0);
				change = 1;
			}
		}
		if (sw2) {
			value = 0;
			if ((ugroup->mute & SND_MIXER_CHN_MASK_STEREO) == 0)
				value = 1;
			if (sw2(ac97, 1, &value) > 0) {
				snd_mixer_element_value_change(file, sw2_element, 0);
				change = 1;
			}
		}
		if (mux_in) {
			snd_ac97_mux(ac97, 0, elements);
			if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_LEFT)
				elements[0] = mux_in;
			if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_RIGHT)
				elements[1] = mux_in;
			if (snd_ac97_mux(ac97, 1, elements) > 0) {
				snd_mixer_element_value_change(file, ac97->me_mux, 0);
				change = 1;
			}
		}
	}
	return change;
}

#define AC97_GROUP_MONO(name, volume1_element, max, sw2_element, mux_in) \
	static int snd_ac97_mixer_group_##name(void *private_data, \
					       snd_kmixer_file_t * file, \
					       int w_flag, \
					       snd_mixer_group_t * ugroup) \
	{ \
		ac97_t *ac97 = (ac97_t *)private_data; \
		return snd_ac97_mixer_group_ctrl2(private_data, file, \
						  w_flag, ugroup, \
						  snd_ac97_volume_##name, \
						  volume1_element, \
						  max, \
						  snd_ac97_mute_##name, \
						  sw2_element, \
						  mux_in); \
	}

#if 0
AC97_GROUP_STEREO(master, ac97->me_vol_master, ac97->max_master, ac97->me_sw_master, ac97->me_mux_mix)
#endif
AC97_GROUP_STEREO(headphone, ac97->me_vol_headphone, ac97->max_headphone, ac97->me_sw_headphone, NULL)
AC97_GROUP_MONO(master_mono, ac97->me_vol_master_mono, ac97->max_master_mono, ac97->me_sw_master_mono, ac97->me_mux_mono_mix)
AC97_GROUP_MONO(phone, ac97->me_vol_phone, 31, ac97->me_sw_phone, ac97->me_mux_phone)
AC97_GROUP_MONO(mic, ac97->me_vol_mic, 31, ac97->me_sw_mic, ac97->me_mux_mic)
AC97_GROUP_STEREO(line, ac97->me_vol_line, 31, ac97->me_sw_line, ac97->me_mux_line)
AC97_GROUP_STEREO(cd, ac97->me_vol_cd, 31, ac97->me_sw_cd, ac97->me_mux_cd)
AC97_GROUP_STEREO(video, ac97->me_vol_video, 31, ac97->me_sw_video, ac97->me_mux_video)
AC97_GROUP_STEREO(aux, ac97->me_vol_aux, 31, ac97->me_sw_aux, ac97->me_mux_aux)
AC97_GROUP_STEREO(pcm, ac97->me_vol_pcm, 31, ac97->me_sw_pcm, NULL)
AC97_GROUP_STEREO(record_gain, ac97->me_vol_igain, 31, NULL, NULL)
AC97_GROUP_MONO(record_gain_mic, ac97->me_vol_igain_mic, 31, NULL, NULL)
AC97_GROUP_MONO(pc_beep, ac97->me_vol_pc_beep, 15, ac97->me_sw_pc_beep, NULL)

static int snd_ac97_mixer_group_master(void *private_data,
				       snd_kmixer_file_t * file,
				       int w_flag,
				       snd_mixer_group_t * ugroup)
{
	ac97_t *ac97 = (ac97_t *)private_data;
	int voices[2];
	snd_kmixer_element_t *elements[2];
	unsigned int bitmap;
	int max, value;
	int change = 0;

	max = ac97->max_master;
	max |= ac97->max_surround;
	max |= ac97->max_center;
	max |= ac97->max_lfe;
	if (!w_flag) {
		ugroup->caps = SND_MIXER_GRPCAP_VOLUME |
			       SND_MIXER_GRPCAP_MUTE |
			       SND_MIXER_GRPCAP_CAPTURE |
			       SND_MIXER_GRPCAP_EXCL_CAPTURE;
		ugroup->channels = SND_MIXER_CHN_MASK_STEREO;
		if (ac97->max_center)
			ugroup->channels |= SND_MIXER_CHN_MASK_FRONT_CENTER;
		if (ac97->max_lfe)
			ugroup->channels |= SND_MIXER_CHN_MASK_WOOFER;
		if (ac97->max_surround)
			ugroup->channels |= SND_MIXER_CHN_MASK_REAR_LEFT |
					    SND_MIXER_CHN_MASK_REAR_RIGHT;
		if (ugroup->channels == SND_MIXER_CHN_MASK_STEREO)
			ugroup->caps |= SND_MIXER_GRPCAP_JOINTLY_MUTE;		

		ugroup->min = 0;
		ugroup->max = max;
		ugroup->capture_group = 1;
		ugroup->capture = 0;
		ugroup->mute = 0;
		
		/* master */
		snd_ac97_volume_master(ac97, 0, voices);
		ugroup->volume.names.front_left = voices[0];
		ugroup->volume.names.front_right = voices[1];
		if (max != ac97->max_master) {
			ugroup->volume.names.front_left <<= 1;
			ugroup->volume.names.front_right <<= 1;
		}
		snd_ac97_mute_master(ac97, 0, &value);
		if (!value)
			ugroup->mute |= SND_MIXER_CHN_MASK_STEREO;

		/* center */
		if (ac97->max_center) {
			snd_ac97_volume_center(ac97, 0, voices);
			ugroup->volume.names.front_center = voices[0];
			if (max != ac97->max_center)
				ugroup->volume.names.front_center <<= 1;
			snd_ac97_mute_center(ac97, 0, &value);
			if (!value)
				ugroup->mute |= SND_MIXER_CHN_MASK_FRONT_CENTER;
		}

		/* lfe */
		if (ac97->max_lfe) {
			snd_ac97_volume_center(ac97, 0, voices);
			ugroup->volume.names.woofer = voices[0];
			if (max != ac97->max_lfe)
				ugroup->volume.names.woofer <<= 1;
			snd_ac97_mute_lfe(ac97, 0, &value);
			if (!value)
				ugroup->mute |= SND_MIXER_CHN_MASK_WOOFER;
		}

		/* surround */
		if (ac97->max_surround) {
			snd_ac97_volume_surround(ac97, 0, voices);
			ugroup->volume.names.rear_left = voices[0];
			ugroup->volume.names.rear_right = voices[1];
			if (max != ac97->max_master) {
				ugroup->volume.names.rear_left <<= 1;
				ugroup->volume.names.rear_right <<= 1;
			}
			snd_ac97_mute_surround(ac97, 0, &bitmap);
			if (!snd_mixer_get_bit(&bitmap, 0))
				ugroup->mute |= SND_MIXER_CHN_MASK_REAR_LEFT;
			if (!snd_mixer_get_bit(&bitmap, 1))
				ugroup->mute |= SND_MIXER_CHN_MASK_REAR_RIGHT;
		}

		/* capture */
		snd_ac97_mux(ac97, 0, elements);
		if (elements[0] == ac97->me_mux_mix)
			ugroup->capture = SND_MIXER_CHN_MASK_FRONT_LEFT;
		if (elements[1] == ac97->me_mux_mix)
			ugroup->capture = SND_MIXER_CHN_MASK_FRONT_RIGHT;
	} else {
		voices[0] = ugroup->volume.names.front_left & max;
		voices[1] = ugroup->volume.names.front_right & max;
		if (max != ac97->max_master) {
			voices[0] >>= 1;
			voices[1] >>= 1;
		}
		if (snd_ac97_volume_master(ac97, 1, voices) > 0) {
			snd_mixer_element_value_change(file, ac97->me_vol_master, 0);
			change = 1;
		}
		value = 0;
		if (!(ugroup->mute & SND_MIXER_CHN_MASK_STEREO))
			value = 1;
		if (snd_ac97_mute_master(ac97, 1, &value) > 0) {
			snd_mixer_element_value_change(file, ac97->me_sw_master, 0);
			change = 1;
		}

		if (ac97->max_center) {
			voices[0] = ugroup->volume.names.front_center & max;
			if (max != ac97->max_center)
				voices[0] >>= 1;
			if (snd_ac97_volume_center(ac97, 1, voices) > 0) {
				snd_mixer_element_value_change(file, ac97->me_vol_center, 0);
				change = 1;
			}
			value = 0;
			if (!(ugroup->mute & SND_MIXER_CHN_MASK_FRONT_CENTER))
				value = 1;
			if (snd_ac97_mute_center(ac97, 1, &value) > 0) {
				snd_mixer_element_value_change(file, ac97->me_sw_center, 0);
				change = 1;
			}
		}

		if (ac97->max_lfe) {
			voices[0] = ugroup->volume.names.woofer & max;
			if (max != ac97->max_lfe)
				voices[0] >>= 1;
			if (snd_ac97_volume_lfe(ac97, 1, voices) > 0) {
				snd_mixer_element_value_change(file, ac97->me_vol_lfe, 0);
				change = 1;
			}
			value = 0;
			if (!(ugroup->mute & SND_MIXER_CHN_MASK_WOOFER))
				value = 1;
			if (snd_ac97_mute_center(ac97, 1, &value) > 0) {
				snd_mixer_element_value_change(file, ac97->me_sw_lfe, 0);
				change = 1;
			}
		}

		if (ac97->max_surround) {
			voices[0] = ugroup->volume.names.rear_left & max;
			voices[1] = ugroup->volume.names.rear_right & max;
			if (max != ac97->max_surround) {
				voices[0] >>= 1;
				voices[1] >>= 1;
			}
			if (snd_ac97_volume_surround(ac97, 1, voices) > 0) {
				snd_mixer_element_value_change(file, ac97->me_vol_surround, 0);
				change = 1;
			}
			bitmap = 0;
			if (!(ugroup->mute & SND_MIXER_CHN_MASK_REAR_LEFT))
				snd_mixer_set_bit(&bitmap, 0, 1);
			if (!(ugroup->mute & SND_MIXER_CHN_MASK_REAR_RIGHT))
				snd_mixer_set_bit(&bitmap, 1, 1);
			if (snd_ac97_mute_surround(ac97, 1, &bitmap) > 0) {
				snd_mixer_element_value_change(file, ac97->me_sw_surround, 0);
				change = 1;
			}
		}

		snd_ac97_mux(ac97, 0, elements);
		if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_LEFT)
			elements[0] = ac97->me_mux_mix;
		if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_RIGHT)
			elements[1] = ac97->me_mux_mix;
		if (snd_ac97_mux(ac97, 1, elements) > 0) {
			snd_mixer_element_value_change(file, ac97->me_mux, 0);
			change = 1;
		}
	}
	return change;
}

static int snd_ac97_mixer_group_bass(void *private_data,
				     snd_kmixer_file_t * file,
				     int w_flag,
				     snd_mixer_group_t * ugroup)
{
	ac97_t *ac97 = (ac97_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_ac97_tone_control(ac97, 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_ac97_tone_control(ac97, 1, &tc) > 0) {
			snd_mixer_element_value_change(file, ac97->me_tone, 0);
			change = 1;
		}
	}
	return change;
}

static int snd_ac97_mixer_group_treble(void *private_data,
				       snd_kmixer_file_t * file,
				       int w_flag,
				       snd_mixer_group_t * ugroup)
{
	ac97_t *ac97 = (ac97_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_ac97_tone_control(ac97, 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_ac97_tone_control(ac97, 1, &tc) > 0) {
			snd_mixer_element_value_change(file, ac97->me_tone, 0);
			change = 1;
		}
	}
	return change;
}

static int snd_ac97_mixer_build(snd_kmixer_t * mixer, ac97_t * ac97, int pcm_count, int *pcm_dev)
{
	snd_kmixer_element_t *element;
	snd_kmixer_group_t *group;
	static struct snd_mixer_element_volume1_range table10_range[2] = {
		{0, 31, -4650, 0},
		{0, 31, -4650, 0}
	};
	static struct snd_mixer_element_volume1_range table12_range[2] = {
		{0, 31, -4500, 0},
		{0, 31, -4500, 0}
	};
	static struct snd_mixer_element_volume1_range table13_range[2] = {
		{0, 31, -3450, 1200},
		{0, 31, -3450, 1200}
	};
	static struct snd_mixer_element_volume1_range table15_range[2] = {
		{0, 15, 0, 2250},
		{0, 15, 0, 2250}
	};
	static snd_mixer_voice_t center_voice[1] = {
		{SND_MIXER_VOICE_CENTER, 0}
	};
	static snd_mixer_voice_t lfe_voice[1] = {
		{SND_MIXER_VOICE_WOOFER, 0}
	};
	static snd_mixer_voice_t surround_voices[2] = {
		{SND_MIXER_VOICE_REAR_LEFT, 0},
		{SND_MIXER_VOICE_REAR_RIGHT, 0}
	};
	struct snd_mixer_element_volume1_range tmp_stereo_range[2];

	if ((ac97->me_accu = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_OUTPUT_ACCU, 0, 0)) == NULL)
		goto __error;
	if (ac97->caps & 0x7c00) {	/* 3D enhancement */
		if ((ac97->me_pcm_accu = snd_mixer_lib_accu1(mixer, "PCM post-3D Accumulator", 0, 0)) == NULL)
			goto __error;
	}
	if ((ac97->me_mono_accu = snd_mixer_lib_accu2(mixer, SND_MIXER_ELEMENT_MONO_OUT_ACCU, 0, 0)) == NULL)
		goto __error;
	ac97->me_mux2_out_mono_accu = ac97->me_mono_accu;
	if ((ac97->me_mono_accu_in = snd_mixer_lib_accu2(mixer, SND_MIXER_ELEMENT_MONO_IN_ACCU, 0, 0)) == NULL)
		goto __error;
	ac97->me_mux_mono_mix = ac97->me_mono_accu_in;
	if ((ac97->me_mux = snd_mixer_lib_mux1(mixer, SND_MIXER_ELEMENT_INPUT_MUX, 0, 0, 2, snd_ac97_mux, ac97)) == NULL)
		goto __error;
	if ((ac97->me_mono_mux = snd_mixer_lib_mux2(mixer, "Mono Output MUX", 0, 0, snd_ac97_mono_out_mux, ac97)) == NULL)
		goto __error;
	/* playback startpoint */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_IN_PCM, 0, SND_MIXER_OSS_PCM, snd_ac97_mixer_group_pcm, ac97)) == NULL)
		goto __error;
	if ((ac97->me_playback = snd_mixer_lib_pcm(mixer, SND_MIXER_ELEMENT_PLAYBACK, 0, SND_MIXER_ETYPE_PLAYBACK, pcm_count, pcm_dev)) == NULL)
		goto __error;
	if ((ac97->me_vol_pcm = snd_mixer_lib_volume1(mixer, "PCM Volume", 0, 2, table13_range, snd_ac97_volume_pcm, ac97)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, ac97->me_vol_pcm) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, ac97->me_playback, ac97->me_vol_pcm) < 0)
		goto __error;
	if ((ac97->me_sw_pcm = snd_mixer_lib_sw2(mixer, "PCM Switch", 0, snd_ac97_mute_pcm, ac97)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, ac97->me_sw_pcm) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, ac97->me_vol_pcm, ac97->me_sw_pcm) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, ac97->me_sw_pcm, ac97->me_accu) < 0)
		goto __error;
	/* build the pc speaker input */
	if (snd_ac97_try_volume_mix(ac97, AC97_PC_BEEP)) {
		if (snd_ac97_build_mono_input(mixer, ac97,
					      SND_MIXER_IN_SPEAKER,
					      snd_ac97_mute_pc_beep,
					      &ac97->me_sw_pc_beep,
					      snd_ac97_volume_pc_beep,
					      &ac97->me_vol_pc_beep,
					      table12_range,
					      NULL,
					      SND_MIXER_OSS_SPEAKER,
					      snd_ac97_mixer_group_pc_beep))
			goto __error;
	}
	/* build the phone input */
	if (snd_ac97_try_volume_mix(ac97, AC97_PHONE)) {
		if (snd_ac97_build_mono_input(mixer, ac97,
					      SND_MIXER_IN_PHONE,
					      snd_ac97_mute_phone,
					      &ac97->me_sw_phone,
					      snd_ac97_volume_phone,
					      &ac97->me_vol_phone,
					      table13_range,
					      &ac97->me_mux_phone,
					      SND_MIXER_OSS_PHONEIN,
					      snd_ac97_mixer_group_phone))
			goto __error;
	}
	/* build bypass accumulator */
	if (ac97->me_sw_pc_beep || ac97->me_sw_phone) {
		if ((ac97->me_bypass_accu = snd_mixer_lib_accu2(mixer, "Bypass Accumulator", 0, 0)) == NULL)
			goto __error;
		if (ac97->me_sw_pc_beep)
			if (snd_mixer_element_route_add(mixer, ac97->me_sw_pc_beep, ac97->me_bypass_accu) < 0)
				goto __error;
		if (ac97->me_sw_phone)
			if (snd_mixer_element_route_add(mixer, ac97->me_sw_phone, ac97->me_bypass_accu) < 0)
				goto __error;
	}
	/* build the mic input */
	if (snd_ac97_try_volume_mix(ac97, AC97_MIC)) {
		if (snd_ac97_build_mono_input(mixer, ac97,
					      SND_MIXER_IN_MIC,
					      snd_ac97_mute_mic,
					      &ac97->me_sw_mic,
					      snd_ac97_volume_mic,
					      &ac97->me_vol_mic,
					      table13_range,
					      &ac97->me_mux_mic,
					      SND_MIXER_OSS_MIC,
					      snd_ac97_mixer_group_mic))
			goto __error;
		if (snd_mixer_element_route_add(mixer, ac97->me_sw_mic, ac97->me_bypass_accu) < 0)
			goto __error;
		ac97->me_mux2_mic = ac97->me_mux_mic;
		if (snd_mixer_element_route_add(mixer, ac97->me_mux_mic, ac97->me_mux) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, ac97->me_mux_mic, ac97->me_mono_mux) < 0)
			goto __error;
	}
	/* build the line input */
	if (snd_ac97_try_volume_mix(ac97, AC97_LINE)) {
		if (snd_ac97_build_stereo_input(mixer, ac97,
						SND_MIXER_IN_LINE,
						snd_ac97_mute_line,
						&ac97->me_sw_line,
						snd_ac97_volume_line,
						&ac97->me_vol_line,
						&ac97->me_mux_line,
						SND_MIXER_OSS_LINE,
						snd_ac97_mixer_group_line))
			goto __error;
		if (snd_mixer_element_route_add(mixer, ac97->me_sw_line, ac97->me_accu) < 0)
			goto __error;
	}
	/* build the CD input */
	if (snd_ac97_try_volume_mix(ac97, AC97_CD)) {
		if (snd_ac97_build_stereo_input(mixer, ac97,
						SND_MIXER_IN_CD,
						snd_ac97_mute_cd,
						&ac97->me_sw_cd,
						snd_ac97_volume_cd,
						&ac97->me_vol_cd,
						&ac97->me_mux_cd,
						SND_MIXER_OSS_CD,
						snd_ac97_mixer_group_cd))
			goto __error;
		if (snd_mixer_element_route_add(mixer, ac97->me_sw_cd, ac97->me_accu) < 0)
			goto __error;
	}
	/* build the VIDEO input */
	if (snd_ac97_try_volume_mix(ac97, AC97_VIDEO)) {
		if (snd_ac97_build_stereo_input(mixer, ac97,
						SND_MIXER_IN_VIDEO,
						snd_ac97_mute_video,
						&ac97->me_sw_video,
						snd_ac97_volume_video,
						&ac97->me_vol_video,
						&ac97->me_mux_video,
						SND_MIXER_OSS_VIDEO,
						snd_ac97_mixer_group_video))
			goto __error;
		if (snd_mixer_element_route_add(mixer, ac97->me_sw_video, ac97->me_accu) < 0)
			goto __error;
	}
	/* build the AUX input */
	if (snd_ac97_try_volume_mix(ac97, AC97_AUX)) {
		if (snd_ac97_build_stereo_input(mixer, ac97,
						SND_MIXER_IN_AUX,
						snd_ac97_mute_aux,
						&ac97->me_sw_aux,
						snd_ac97_volume_aux,
						&ac97->me_vol_aux,
						&ac97->me_mux_aux,
						SND_MIXER_OSS_LINE1,
						snd_ac97_mixer_group_aux))
			goto __error;
		if (snd_mixer_element_route_add(mixer, ac97->me_sw_aux, ac97->me_accu) < 0)
			goto __error;
	}
	/* build 3D control */
	element = ac97->me_accu;
	if (ac97->caps & 0x7c00) {	/* 3D enhancement */
		struct snd_mixer_element_3d_effect1_info info;
		
		memset(&info, 0, sizeof(info));
		if (ac97->id == 0x83847608) {	// Sigmatel 9708
			info.effect = SND_MIXER_EFF1_SW | SND_MIXER_EFF1_DEPTH | SND_MIXER_EFF1_DEPTH_REAR;
			snd_ac97_change_3d_params(ac97);
			info.max_depth = info.max_depth_rear = ac97->max_3d;
		} else {
			info.effect = SND_MIXER_EFF1_SW | SND_MIXER_EFF1_CENTER | SND_MIXER_EFF1_DEPTH;
			snd_ac97_change_3d_params(ac97);
			info.max_center = info.max_depth = ac97->max_3d;
		}
		if ((element = snd_mixer_lib_3d_effect1(mixer,
						SND_MIXER_GRP_EFFECT, 0,
						&info,
						snd_ac97_3d,
						ac97)) == NULL)
			goto __error;
		if (snd_mixer_element_route_add(mixer, ac97->me_accu, element) < 0)
			goto __error;
	}
	if (ac97->me_pcm_accu) {
		if (snd_mixer_element_route_add(mixer, element, ac97->me_pcm_accu) < 0)
			goto __error;
		element = ac97->me_pcm_accu;
	}
	if (snd_mixer_element_route_add(mixer, element, ac97->me_mono_accu) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, ac97->me_mono_accu, ac97->me_mono_mux) < 0)
		goto __error;
	if (ac97->me_bypass_accu) {
		if (snd_mixer_element_route_add(mixer, element, ac97->me_bypass_accu) < 0)
			goto __error;
		element = ac97->me_bypass_accu;
	}
	/* build bass & treble control */
	if (snd_ac97_try_volume_mix(ac97, AC97_MASTER_TONE)) {
	        struct snd_mixer_element_tone_control1_info info;
	        
	        memset(&info, 0, sizeof(info));
	        info.tc = SND_MIXER_TC1_SW | SND_MIXER_TC1_BASS | SND_MIXER_TC1_TREBLE;
	        info.max_bass = info.max_treble = 14;
		info.min_bass_dB = info.min_treble_dB = -1050;
		info.min_treble_dB = info.max_treble_dB = 1050;
		if ((ac97->me_tone = snd_mixer_lib_tone_control1(mixer, SND_MIXER_ELEMENT_TONE_CONTROL, 0, &info, snd_ac97_tone_control, ac97)) == NULL)
			goto __error;
		if (snd_mixer_element_route_add(mixer, element, ac97->me_tone) < 0)
			goto __error;
		element = ac97->me_tone;
		if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_GRP_TREBLE, 0, SND_MIXER_OSS_BASS, snd_ac97_mixer_group_bass, ac97)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, element))
			goto __error;
		if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_GRP_TREBLE, 0, SND_MIXER_OSS_TREBLE, snd_ac97_mixer_group_treble, ac97)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, element))
			goto __error;
	}
	/* make a connection to input mixer */
	ac97->me_mux_mix = element;
	if (snd_mixer_element_route_add(mixer, element, ac97->me_mux) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element, ac97->me_mono_accu_in) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, ac97->me_mono_accu_in, ac97->me_mux) < 0)
		goto __error;
	/* build master volume out */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_OUT_MASTER, 0, SND_MIXER_OSS_VOLUME, snd_ac97_mixer_group_master, ac97)) == NULL)
		goto __error;
	snd_ac97_change_volume_params1(ac97, AC97_MASTER, &ac97->max_master);
	memcpy(&tmp_stereo_range, &table10_range, sizeof(tmp_stereo_range));
	if (ac97->max_master == 63) {
		tmp_stereo_range[0].max = tmp_stereo_range[1].max = 63;
		tmp_stereo_range[0].min_dB = tmp_stereo_range[1].min_dB = -9450;
	}
	if ((ac97->me_vol_master = snd_mixer_lib_volume1(mixer, "Master Volume", 0, 2, tmp_stereo_range, snd_ac97_volume_master, ac97)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, ac97->me_vol_master) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element, ac97->me_vol_master) < 0)
		goto __error;
	if ((ac97->me_sw_master = snd_mixer_lib_sw2(mixer, "Master Switch", 0, snd_ac97_mute_master, ac97)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, ac97->me_sw_master) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, ac97->me_vol_master, ac97->me_sw_master) < 0)
		goto __error;
	if ((ac97->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, ac97->me_sw_master, ac97->me_out_master) < 0)
		goto __error;
	if (snd_ac97_try_volume_mix(ac97, AC97_CENTER_LFE_MASTER)) {
		if ((ac97->me_in_center = snd_mixer_lib_io(mixer, SND_MIXER_IN_CENTER, 0, SND_MIXER_ETYPE_INPUT, 0, 1, center_voice)) == NULL)
			goto __error;
		snd_ac97_change_volume_params1(ac97, AC97_CENTER_LFE_MASTER, &ac97->max_center);
		memcpy(&tmp_stereo_range, &table10_range, sizeof(tmp_stereo_range));
		if (ac97->max_center == 63) {
			tmp_stereo_range[0].max = 63;
			tmp_stereo_range[0].min_dB = -9450;
		}
		if ((ac97->me_vol_center = snd_mixer_lib_volume1(mixer, "Center Volume", 0, 1, tmp_stereo_range, snd_ac97_volume_center, ac97)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, ac97->me_vol_center) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, ac97->me_in_center, ac97->me_vol_center) < 0)
			goto __error;
		if ((ac97->me_sw_center = snd_mixer_lib_sw2(mixer, "Center Switch", 0, snd_ac97_mute_center, ac97)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, ac97->me_sw_center) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, ac97->me_vol_center, ac97->me_sw_center) < 0)
			goto __error;
		if ((ac97->me_out_center = snd_mixer_lib_io(mixer, SND_MIXER_OUT_CENTER, 0, SND_MIXER_ETYPE_OUTPUT, 0, 1, center_voice)) == NULL)
			goto __error;
		if (snd_mixer_element_route_add(mixer, ac97->me_sw_center, ac97->me_out_center) < 0)
			goto __error;
	}
	if (snd_ac97_try_volume_mix(ac97, AC97_CENTER_LFE_MASTER + 1)) {
		if ((ac97->me_in_lfe = snd_mixer_lib_io(mixer, SND_MIXER_IN_WOOFER, 0, SND_MIXER_ETYPE_INPUT, 0, 1, lfe_voice)) == NULL)
			goto __error;
		snd_ac97_change_volume_params2(ac97, AC97_CENTER_LFE_MASTER, &ac97->max_lfe);
		memcpy(&tmp_stereo_range, &table10_range, sizeof(tmp_stereo_range));
		if (ac97->max_lfe == 63) {
			tmp_stereo_range[0].max = 63;
			tmp_stereo_range[0].min_dB = -9450;
		}
		if ((ac97->me_vol_lfe = snd_mixer_lib_volume1(mixer, "Woofer Volume", 0, 1, tmp_stereo_range, snd_ac97_volume_lfe, ac97)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, ac97->me_vol_lfe) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, ac97->me_in_lfe, ac97->me_vol_lfe) < 0)
			goto __error;
		if ((ac97->me_sw_lfe = snd_mixer_lib_sw2(mixer, "Woofer Switch", 0, snd_ac97_mute_lfe, ac97)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, ac97->me_sw_lfe) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, ac97->me_vol_lfe, ac97->me_sw_lfe) < 0)
			goto __error;
		if ((ac97->me_out_lfe = snd_mixer_lib_io(mixer, SND_MIXER_OUT_WOOFER, 0, SND_MIXER_ETYPE_OUTPUT, 0, 1, lfe_voice)) == NULL)
			goto __error;
		if (snd_mixer_element_route_add(mixer, ac97->me_sw_lfe, ac97->me_out_lfe) < 0)
			goto __error;
	}
	if (snd_ac97_try_volume_mix(ac97, AC97_SURROUND_MASTER)) {
		if ((ac97->me_in_surround = snd_mixer_lib_io(mixer, SND_MIXER_IN_SURROUND, 0, SND_MIXER_ETYPE_INPUT, 0, 2, surround_voices)) == NULL)
			goto __error;
		snd_ac97_change_volume_params1(ac97, AC97_SURROUND_MASTER, &ac97->max_surround);
		memcpy(&tmp_stereo_range, &table10_range, sizeof(tmp_stereo_range));
		if (ac97->max_surround == 63) {
			tmp_stereo_range[0].max = tmp_stereo_range[1].max = 63;
			tmp_stereo_range[0].min_dB = tmp_stereo_range[1].min_dB = -9450;
		}
		if ((ac97->me_vol_surround = snd_mixer_lib_volume1(mixer, "Surround Volume", 0, 2, tmp_stereo_range, snd_ac97_volume_surround, ac97)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, ac97->me_vol_surround) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, ac97->me_in_surround, ac97->me_vol_surround) < 0)
			goto __error;
		if ((ac97->me_sw_surround = snd_mixer_lib_sw1(mixer, "Surround Switch", 0, 2, snd_ac97_mute_surround, ac97)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, ac97->me_sw_surround) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, ac97->me_vol_surround, ac97->me_sw_surround) < 0)
			goto __error;
		if ((ac97->me_out_surround = snd_mixer_lib_io(mixer, SND_MIXER_OUT_SURROUND, 0, SND_MIXER_ETYPE_OUTPUT, 0, 2, surround_voices)) == NULL)
			goto __error;
		if (snd_mixer_element_route_add(mixer, ac97->me_sw_surround, ac97->me_out_surround) < 0)
			goto __error;
	}

	/* build headphone out */
	if (snd_ac97_try_volume_mix(ac97, AC97_HEADPHONE)) {
		if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_OUT_HEADPHONE, 0, SND_MIXER_OSS_PHONEOUT, snd_ac97_mixer_group_headphone, ac97)) == NULL)
			goto __error;
		snd_ac97_change_volume_params1(ac97, AC97_HEADPHONE, &ac97->max_headphone);
		memcpy(&tmp_stereo_range, &table10_range, sizeof(tmp_stereo_range));
		if (ac97->max_master == 63) {
			tmp_stereo_range[0].max = tmp_stereo_range[1].max = 63;
			tmp_stereo_range[0].min_dB = tmp_stereo_range[1].min_dB = -9450;
		}
		if ((ac97->me_vol_headphone = snd_mixer_lib_volume1(mixer, "Headphone Volume", 0, 2, tmp_stereo_range, snd_ac97_volume_headphone, ac97)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, ac97->me_vol_headphone) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, element, ac97->me_vol_headphone) < 0)
			goto __error;
		if ((ac97->me_sw_headphone = snd_mixer_lib_sw2(mixer, "Headphone Switch", 0, snd_ac97_mute_headphone, ac97)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, ac97->me_sw_headphone) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, ac97->me_vol_headphone, ac97->me_sw_headphone) < 0)
			goto __error;
		if ((ac97->me_out_headphone = snd_mixer_lib_io_stereo(mixer, SND_MIXER_OUT_HEADPHONE, 0, SND_MIXER_ETYPE_OUTPUT, 0)) == NULL)
			goto __error;
		if (snd_mixer_element_route_add(mixer, ac97->me_sw_headphone, ac97->me_out_headphone) < 0)
			goto __error;
	}
	/* build master mono out */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_OUT_MASTER_MONO, 0, SND_MIXER_OSS_LINE2, snd_ac97_mixer_group_master_mono, ac97)) == NULL)
		goto __error;
	snd_ac97_change_volume_params1(ac97, AC97_MASTER_MONO, &ac97->max_master);
	memcpy(&tmp_stereo_range, &table10_range, sizeof(tmp_stereo_range));
	if (ac97->max_master == 63) {
		tmp_stereo_range[0].max = 63;
		tmp_stereo_range[0].min_dB = -9450;
	}
	if ((ac97->me_vol_master_mono = snd_mixer_lib_volume1(mixer, "Master Mono Volume", 0, 1, tmp_stereo_range, snd_ac97_volume_master_mono, ac97)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, ac97->me_vol_master_mono) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, ac97->me_mono_mux, ac97->me_vol_master_mono) < 0)
		goto __error;
	if ((ac97->me_sw_master_mono = snd_mixer_lib_sw2(mixer, "Master Mono Switch", 0, snd_ac97_mute_master_mono, ac97)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, ac97->me_sw_master_mono) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, ac97->me_vol_master_mono, ac97->me_sw_master_mono) < 0)
		goto __error;
	if ((ac97->me_out_master_mono = snd_mixer_lib_io_mono(mixer, SND_MIXER_OUT_MASTER_MONO, 0, SND_MIXER_ETYPE_OUTPUT, 0)) == NULL)
		goto __error;
	if (snd_mixer_element_route_add(mixer, ac97->me_sw_master_mono, ac97->me_out_master_mono) < 0)
		goto __error;

	/* PCM record */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_GRP_IGAIN, 0, SND_MIXER_OSS_IGAIN, snd_ac97_mixer_group_record_gain, ac97)) == NULL)
		goto __error;
	if ((ac97->me_vol_igain = snd_mixer_lib_volume1(mixer, "Input Gain Volume", 0, 2, table15_range, snd_ac97_volume_record_gain, ac97)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, ac97->me_vol_igain) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, ac97->me_mux, ac97->me_vol_igain) < 0)
		goto __error;
	if ((ac97->me_sw_igain = snd_mixer_lib_sw2(mixer, "Input Gain Switch", 0, snd_ac97_mute_record_gain, ac97)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, ac97->me_sw_igain) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, ac97->me_vol_igain, ac97->me_sw_igain) < 0)
		goto __error;
	if ((ac97->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, ac97->me_sw_igain, ac97->me_capture) < 0)
		goto __error;

	/* PCM record, MIC gain */
	if (snd_ac97_try_volume_mix(ac97, AC97_REC_GAIN_MIC)) {
		if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_GRP_MIC_GAIN, 0, SND_MIXER_OSS_DIGITAL1, snd_ac97_mixer_group_record_gain_mic, ac97)) == NULL)
			goto __error;
		if ((ac97->me_vol_igain_mic = snd_mixer_lib_volume1(mixer, "MIC Gain Volume", 0, 1, tmp_stereo_range, snd_ac97_volume_record_gain_mic, ac97)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, ac97->me_vol_igain_mic) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, ac97->me_mux, ac97->me_vol_igain_mic) < 0)
			goto __error;
		if ((ac97->me_sw_igain_mic = snd_mixer_lib_sw2(mixer, "MIC Gain Switch", 0, snd_ac97_mute_record_gain_mic, ac97)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, ac97->me_sw_igain_mic) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, ac97->me_vol_igain_mic, ac97->me_sw_igain_mic) < 0)
			goto __error;
		if ((ac97->me_capture = snd_mixer_lib_pcm(mixer, SND_MIXER_ELEMENT_CAPTURE, 0, SND_MIXER_ETYPE_CAPTURE, 1, pcm_dev + 2)) == NULL)
			goto __error;
		if (snd_mixer_element_route_add(mixer, ac97->me_sw_igain_mic, ac97->me_capture) < 0)
			goto __error;
	}
	return 0;
	
      __error:
	return -ENXIO;
}

static inline int printable(unsigned int x)
{
	x &= 0xff;
	if (x < ' ' || x >= 0x7f)
		return '?';
	return x;
}

snd_kmixer_t *snd_ac97_mixer(snd_card_t * card, ac97_t * ac97, int pcm_count, int *pcm_dev)
{
	int idx;
	snd_kmixer_t *mixer;

	if (!card || !ac97)
		return NULL;
	ac97->reg_lock = SPIN_LOCK_UNLOCKED;
	mixer = snd_mixer_new(card, "AC97");
	if (!mixer)
		return NULL;
	strcpy(mixer->name, "AC97 generic");
	ac97->write(ac97->private_data, AC97_RESET, 0);		/* reset to defaults */
	ac97->id = ac97->read(ac97->private_data, AC97_VENDOR_ID1) << 16;
	ac97->id |= ac97->read(ac97->private_data, AC97_VENDOR_ID2);
	ac97->caps = ac97->read(ac97->private_data, AC97_RESET);
	ac97->write(ac97->private_data, AC97_POWERDOWN, 0);	/* nothing should be in powerdown mode */
	ac97->write(ac97->private_data, AC97_GENERAL_PURPOSE, 0);
	if (snd_ac97_mixer_build(mixer, ac97, pcm_count, pcm_dev) < 0) {
		snd_mixer_free(mixer);
		return NULL;
	}
	for (idx = 0; idx < AC97_SWITCHES; idx++)
		snd_mixer_switch_new(mixer, &snd_ac97_switches[idx], ac97);
	mixer->private_data = ac97;
	mixer->private_free = snd_ac97_mixer_free;
	ac97->write(ac97->private_data, AC97_RESET, 0);		/* reset to defaults */
	snd_delay(1);
	ac97->write(ac97->private_data, AC97_POWERDOWN, 0);	/* nothing should be in powerdown mode */
	sprintf(ac97->name, "0x%x %c%c%c", ac97->id,
		printable(ac97->id >> 24),
		printable(ac97->id >> 16),
		printable(ac97->id >> 8));
#if 0
	snd_printk("ac97: id = 0x%x '%c%c%c', caps = 0x%x\n", ac97->id, (unsigned char) (ac97->id >> 24), (unsigned char) (ac97->id >> 16), (unsigned char) (ac97->id >> 8), ac97->caps);
#endif
	for (idx = 0; snd_ac97_codec_ids[idx].id; idx++)
		if (snd_ac97_codec_ids[idx].id == ac97->id) {
			strcpy(ac97->name, snd_ac97_codec_ids[idx].name);
			strcpy(mixer->name, ac97->name);
			ac97->rev_is_not_rev = 1;
			goto __skip;
		}
	for (idx = 0; snd_ac97_codec_ids[idx].id; idx++)
		if ((snd_ac97_codec_ids[idx].id & 0xffffff00) == (ac97->id & 0xffffff00)) {
			strcpy(ac97->name, snd_ac97_codec_ids[idx].name);
			strcpy(mixer->name, ac97->name);
			break;
		}
      __skip:
	snd_ac97_proc_init(card, ac97);
	return mixer;
}

/*

 */

static void snd_ac97_proc_read(snd_info_buffer_t * buffer, void *private_data)
{
	unsigned short val;
	ac97_t *ac97 = (ac97_t *) private_data;

	if (ac97->rev_is_not_rev) {
		snd_iprintf(buffer, "%s\n\n", ac97->name);
	} else {
		snd_iprintf(buffer, "%s rev %i\n\n", ac97->name, ac97->id & 0xff);
	}
	snd_iprintf(buffer, "Capabilities     :%s%s%s%s%s%s\n",
	    ac97->caps & 0x0001 ? " -dedicated MIC PCM IN channel-" : "",
		    ac97->caps & 0x0002 ? " -reserved1-" : "",
		    ac97->caps & 0x0004 ? " -bass & treble-" : "",
		    ac97->caps & 0x0008 ? " -simulated stereo-" : "",
		    ac97->caps & 0x0010 ? " -headphone out-" : "",
		    ac97->caps & 0x0020 ? " -loudness-" : "");
	val = ac97->caps & 0x00c0;
	snd_iprintf(buffer, "DAC resolution   : %s%s%s%s\n",
		    val == 0x0000 ? "16-bit" : "",
		    val == 0x0040 ? "18-bit" : "",
		    val == 0x0080 ? "20-bit" : "",
		    val == 0x00c0 ? "???" : "");
	val = ac97->caps & 0x0030;
	snd_iprintf(buffer, "ADC resolution   : %s%s%s%s\n",
		    val == 0x0000 ? "16-bit" : "",
		    val == 0x0010 ? "18-bit" : "",
		    val == 0x0020 ? "20-bit" : "",
		    val == 0x0030 ? "???" : "");
	snd_iprintf(buffer, "3D enhancement   : %s\n",
		snd_ac97_stereo_enhancements[(ac97->caps >> 10) & 0x1f]);
	snd_iprintf(buffer, "\nCurrent setup\n");
	val = ac97->read(ac97->private_data, AC97_MIC);
	snd_iprintf(buffer, "MIC gain         : %s\n", val & 0x0040 ? "+20dB" : "+0dB");
	val = ac97->read(ac97->private_data, AC97_GENERAL_PURPOSE);
	snd_iprintf(buffer, "POP path         : %s 3D\n"
		    "Sim. stereo      : %s\n"
		    "3D enhancement   : %s\n"
		    "Loudness         : %s\n"
		    "Mono output      : %s\n"
		    "MIC select       : %s\n"
		    "ADC/DAC loopback : %s\n",
		    val & 0x8000 ? "post" : "pre",
		    val & 0x4000 ? "on" : "off",
		    val & 0x2000 ? "on" : "off",
		    val & 0x1000 ? "on" : "off",
		    val & 0x0200 ? "MIC" : "MIX",
		    val & 0x0100 ? "MIC2" : "MIC1",
		    val & 0x0080 ? "on" : "off");
}

#if 0
static void snd_ac97_outm(ac97_t * ac97, unsigned short reg, unsigned short mask, unsigned short val)
{
	unsigned long flags;
	unsigned short old;

	spin_lock_irqsave(&ac97->reg_lock, flags);
	old = ac97->read(ac97->private_data, reg);
	ac97->write(ac97->private_data, reg, (old & mask) | val);
	spin_unlock_irqrestore(&ac97->reg_lock, flags);
}
#endif

static void snd_ac97_proc_init(snd_card_t * card, ac97_t * ac97)
{
	snd_info_entry_t *entry;

	if ((entry = snd_info_create_entry(card, "ac97")) != NULL) {
		entry->private_data = ac97;
		entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
		entry->t.text.read_size = 512;
		entry->t.text.read = snd_ac97_proc_read;
		if (snd_info_register(entry) < 0) {
			snd_info_free_entry(entry);
			entry = NULL;
		}
	}
	ac97->proc_entry = entry;
}

static void snd_ac97_proc_done(ac97_t * ac97)
{
	if (ac97->proc_entry) {
		snd_info_unregister(ac97->proc_entry);
		ac97->proc_entry = NULL;
	}
}

EXPORT_SYMBOL(snd_ac97_mixer);

/*
 *  INIT part
 */

int init_module(void)
{
	return 0;
}

void cleanup_module(void)
{
}
