/*
 *  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 "driver.h"
#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"	},
	{	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(int w_flag, snd_kmixer_element_t **elements, ac97_t *ac97)
{
	unsigned long flags;
	int change = 0;
	snd_kmixer_element_t *left, *right;
	int nleft, nright;

	snd_spin_lock(ac97, access, &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));
	}
	snd_spin_unlock(ac97, access, &flags);
	return change;
}

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

	snd_spin_lock(ac97, access, &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]);
	}
	snd_spin_unlock(ac97, access, &flags);
	return change;
}

static int snd_ac97_mute(int w_flag, int *value, ac97_t *ac97, unsigned char reg)
{
	unsigned long flags;
	unsigned short val;
	int change = 0;
	
	snd_spin_lock(ac97, access, &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);
	}
	snd_spin_unlock(ac97, access, &flags);
	return change;
}

static int snd_ac97_mute_master(int w_flag, int *value, ac97_t *ac97)
{
	return snd_ac97_mute(w_flag, value, ac97, AC97_MASTER);
}

static int snd_ac97_mute_headphone(int w_flag, int *value, ac97_t *ac97)
{
	return snd_ac97_mute(w_flag, value, ac97, AC97_HEADPHONE);
}

static int snd_ac97_mute_master_mono(int w_flag, int *value, ac97_t *ac97)
{
	return snd_ac97_mute(w_flag, value, ac97, AC97_MASTER_MONO);
}

static int snd_ac97_mute_pc_beep(int w_flag, int *value, ac97_t *ac97)
{
	return snd_ac97_mute(w_flag, value, ac97, AC97_PC_BEEP);
}

static int snd_ac97_mute_phone(int w_flag, int *value, ac97_t *ac97)
{
	return snd_ac97_mute(w_flag, value, ac97, AC97_PHONE);
}

static int snd_ac97_mute_mic(int w_flag, int *value, ac97_t *ac97)
{
	return snd_ac97_mute(w_flag, value, ac97, AC97_MIC);
}

static int snd_ac97_mute_line(int w_flag, int *value, ac97_t *ac97)
{
	return snd_ac97_mute(w_flag, value, ac97, AC97_LINE);
}

static int snd_ac97_mute_cd(int w_flag, int *value, ac97_t *ac97)
{
	return snd_ac97_mute(w_flag, value, ac97, AC97_CD);
}

static int snd_ac97_mute_video(int w_flag, int *value, ac97_t *ac97)
{
	return snd_ac97_mute(w_flag, value, ac97, AC97_VIDEO);
}

static int snd_ac97_mute_aux(int w_flag, int *value, ac97_t *ac97)
{
	return snd_ac97_mute(w_flag, value, ac97, AC97_AUX);
}

static int snd_ac97_mute_pcm(int w_flag, int *value, ac97_t *ac97)
{
	return snd_ac97_mute(w_flag, value, ac97, AC97_PCM);
}

static int snd_ac97_mute_record_gain(int w_flag, int *value, ac97_t *ac97)
{
	return snd_ac97_mute(w_flag, value, ac97, AC97_REC_GAIN);
}

static int snd_ac97_mute_record_gain_mic(int w_flag, int *value, ac97_t *ac97)
{
	return snd_ac97_mute(w_flag, value, ac97, AC97_REC_GAIN_MIC);
}

static int snd_ac97_tone_control(int w_flag,
			struct snd_mixer_element_tone_control1 *tc1,
			ac97_t *ac97)
{
	unsigned long flags;
	unsigned char bass, treble;
	int change = 0;
	
	snd_spin_lock(ac97, access, &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]);
	}
	snd_spin_unlock(ac97, access, &flags);
	return change;	
}

static int snd_ac97_volume_stereo(int w_flag, int *volume, ac97_t *ac97, int reg, int max)
{
	unsigned long flags;
	unsigned char left, right;
	int change = 0;
	
	snd_spin_lock(ac97, access, &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]);
	}
	snd_spin_unlock(ac97, access, &flags);
	return change;
}

static int snd_ac97_volume_mono(int w_flag, int *volume, ac97_t *ac97, int reg, int max)
{
	unsigned long flags;
	unsigned char val;
	int change = 0;
	
	snd_spin_lock(ac97, access, &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]);
	}
	snd_spin_unlock(ac97, access, &flags);
	return change;
}

static int snd_ac97_master_volume(int w_flag, int *volume, ac97_t *ac97)
{
	return snd_ac97_volume_stereo(w_flag, volume, ac97,
					AC97_MASTER, ac97->max_master);
}

static int snd_ac97_headphone_volume(int w_flag, int *volume, ac97_t *ac97)
{
	return snd_ac97_volume_stereo(w_flag, volume, ac97,
					AC97_HEADPHONE, ac97->max_headphone);
}

static int snd_ac97_master_mono_volume(int w_flag, int *volume, ac97_t *ac97)
{
	return snd_ac97_volume_mono(w_flag, volume, ac97,
					AC97_MASTER_MONO, ac97->max_master_mono);
}

static int snd_ac97_pc_beep_volume(int w_flag, int *volume, ac97_t *ac97)
{
	unsigned long flags;
	unsigned char val;
	int change = 0;
	
	snd_spin_lock(ac97, access, &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]);
	}
	snd_spin_unlock(ac97, access, &flags);
	return change;
}

static int snd_ac97_phone_volume(int w_flag, int *volume, ac97_t *ac97)
{
	return snd_ac97_volume_mono(w_flag, volume, ac97,
					AC97_PHONE, 31);
}

static int snd_ac97_mic_volume(int w_flag, int *volume, ac97_t *ac97)
{
	return snd_ac97_volume_mono(w_flag, volume, ac97,
					AC97_MIC, 31);
}

static int snd_ac97_line_volume(int w_flag, int *volume, ac97_t *ac97)
{
	return snd_ac97_volume_stereo(w_flag, volume, ac97,
					AC97_LINE, 31);
}

static int snd_ac97_cd_volume(int w_flag, int *volume, ac97_t *ac97)
{
	return snd_ac97_volume_stereo(w_flag, volume, ac97,
					AC97_CD, 31);
}

static int snd_ac97_video_volume(int w_flag, int *volume, ac97_t *ac97)
{
	return snd_ac97_volume_stereo(w_flag, volume, ac97,
					AC97_VIDEO, 31);
}

static int snd_ac97_aux_volume(int w_flag, int *volume, ac97_t *ac97)
{
	return snd_ac97_volume_stereo(w_flag, volume, ac97,
					AC97_AUX, 31);
}

static int snd_ac97_pcm_volume(int w_flag, int *volume, ac97_t *ac97)
{
	return snd_ac97_volume_stereo(w_flag, volume, ac97,
					AC97_PCM, 31);
}

static int snd_ac97_record_gain_volume(int w_flag, int *volume, ac97_t *ac97)
{
	return snd_ac97_volume_stereo(w_flag, volume, ac97,
					AC97_REC_GAIN, 31);
}

static int snd_ac97_record_gain_mic_volume(int w_flag, int *volume, ac97_t *ac97)
{
	return snd_ac97_volume_mono(w_flag, volume, ac97,
					AC97_REC_GAIN_MIC, 31);
}

static int snd_ac97_mic_boost_volume(int w_flag, int *volume, ac97_t *ac97)
{
	unsigned long flags;
	unsigned char val;
	int change = 0;
	
	snd_spin_lock(ac97, access, &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]);
	}
	snd_spin_unlock(ac97, access, &flags);
	return change;
}

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

	snd_spin_lock(ac97, access, &flags);
	sw = (ac97->regs[AC97_GENERAL_PURPOSE] & 0x2000) ? 1 : 0;
	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;
		effect1->center = center;
		effect1->depth = depth;
	} else {
		change = effect1->sw != sw ||
		         effect1->center != center ||
		         effect1->depth != depth;
		ac97->regs[AC97_GENERAL_PURPOSE] &= ~0x2000;
		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]);
	}
	snd_spin_unlock(ac97, access, &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;
	snd_spin_lock(ac97, access, &flags);
	val = ac97->regs[AC97_GENERAL_PURPOSE];
	uswitch->value.enable = val & kswitch->private_value ? 1 : 0;
	snd_spin_unlock(ac97, access, &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;
	snd_spin_lock(ac97, access, &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);
	snd_spin_unlock(ac97, access, &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;
	}
	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, val = 0x8000);
}

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

	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 snd_kmixer_element_t *snd_ac97_build_mono_input(
					snd_kmixer_t *mixer,
					ac97_t *ac97,
					char *name,
					snd_mixer_sw2_control_t *sw_control,
					snd_mixer_volume1_control_t *vol_control,
					struct snd_mixer_element_volume1_range *range)
{
	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(mixer, name, 0)) == 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_mixer_sw2_control_t *)snd_ac97_mute_mic) {
		if ((element2 = snd_mixer_lib_volume1(mixer, "MIC Boost", 0, 1, mic_range, (snd_mixer_volume1_control_t *)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;
	}
	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;
	return element3;

      __error:
      	return NULL; 
}

static snd_kmixer_element_t *snd_ac97_build_stereo_input(
					snd_kmixer_t *mixer,
					ac97_t *ac97,
					char *name,
					snd_mixer_sw2_control_t *sw_control,
					snd_mixer_volume1_control_t *vol_control)
{
	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(mixer, name, 0)) == NULL)
		goto __error;
	if ((element1 = snd_mixer_lib_io_stereo(mixer, name, 0, SND_MIXER_ETYPE_INPUT, 0)) == NULL)
		goto __error;
	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;
	return element3;

      __error:
      	return NULL; 
}

static int snd_ac97_mixer_build(snd_kmixer_t * mixer, ac97_t * ac97, int pcm_count, int *pcm_dev)
{
	snd_kmixer_group_t *group;
	snd_kmixer_element_t *out_accu, *out_mono_accu, *out_pcm_accu;
	snd_kmixer_element_t *out_bypass_accu, *in_mono_accu, *mux;
	snd_kmixer_element_t *out_mono_mux;
	snd_kmixer_element_t *element1, *element2, *element3, *element4;
	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}
	};
	struct snd_mixer_element_volume1_range tmp_stereo_range[2];

	out_bypass_accu = NULL;
	if ((out_accu = snd_mixer_lib_accu1(mixer, SND_MIXER_ELEMENT_OUTPUT_ACCU, 0, 0)) == NULL)
		goto __error;
	if (ac97->caps & 0x7c00) {	/* 3D enhancement */
		if ((out_pcm_accu = snd_mixer_lib_accu1(mixer, "PCM post-3D Accumulator", 0, 0)) == NULL)
			goto __error;
	} else {
		out_pcm_accu = NULL;
	}
	if ((out_mono_accu = snd_mixer_lib_accu2(mixer, SND_MIXER_ELEMENT_MONO_OUT_ACCU, 0, 0)) == NULL)
		goto __error;
	ac97->me_mux2_out_mono_accu = out_mono_accu;
	if ((in_mono_accu = snd_mixer_lib_accu2(mixer, SND_MIXER_ELEMENT_MONO_IN_ACCU, 0, 0)) == NULL)
		goto __error;
	ac97->me_mux_mono_mix = in_mono_accu;
	if ((mux = snd_mixer_lib_mux1(mixer, SND_MIXER_ELEMENT_INPUT_MUX, 0, 0, 2, (snd_mixer_mux1_control_t *)snd_ac97_mux, ac97)) == NULL)
		goto __error;
	if ((out_mono_mux = snd_mixer_lib_mux2(mixer, "Mono Output MUX", 0, 0, (snd_mixer_mux2_control_t *)snd_ac97_mono_out_mux, ac97)) == NULL)
		goto __error;
	/* playback startpoint */
	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, pcm_count, pcm_dev)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "PCM Volume", 0, 2, table13_range, (snd_mixer_volume1_control_t *)snd_ac97_pcm_volume, 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;
	if ((element3 = snd_mixer_lib_sw2(mixer, "PCM Switch", 0, (snd_mixer_sw2_control_t *)snd_ac97_mute_pcm, 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;
	if (snd_mixer_element_route_add(mixer, element3, out_accu) < 0)
		goto __error;
	/* prepare variables for the bypass accumulator */
	element1 = element2 = NULL;
	/* build the pc speaker input */
	if (snd_ac97_try_volume_mix(ac97, AC97_PC_BEEP)) {
		if ((element1 = snd_ac97_build_mono_input(mixer, ac97,
						SND_MIXER_IN_SPEAKER,
						(snd_mixer_sw2_control_t *)snd_ac97_mute_pc_beep,
						(snd_mixer_volume1_control_t *)snd_ac97_pc_beep_volume,
						table12_range)) == NULL)
			goto __error;
	}
	/* build the phone input */
	if (snd_ac97_try_volume_mix(ac97, AC97_PHONE)) {
		if ((element2 = snd_ac97_build_mono_input(mixer, ac97,
						SND_MIXER_IN_PHONE,
						(snd_mixer_sw2_control_t *)snd_ac97_mute_phone,
						(snd_mixer_volume1_control_t *)snd_ac97_phone_volume,
						table13_range)) == NULL)
			goto __error;
		ac97->me_mux_phone = element2;
		if (snd_mixer_element_route_add(mixer, element1, mux) < 0)
			goto __error;
	}
	/* build bypass accumulator */
	if (element1 || element2) {
		if ((out_bypass_accu = snd_mixer_lib_accu2(mixer, "Bypass Accumulator", 0, 0)) == NULL)
			goto __error;
		if (snd_mixer_element_route_add(mixer, element1, out_bypass_accu) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, element2, out_bypass_accu) < 0)
			goto __error;
	}
	/* build the mic input */
	if (snd_ac97_try_volume_mix(ac97, AC97_MIC)) {
		if ((element1 = snd_ac97_build_mono_input(mixer, ac97,
						SND_MIXER_IN_MIC,
						(snd_mixer_sw2_control_t *)snd_ac97_mute_mic,
						(snd_mixer_volume1_control_t *)snd_ac97_mic_volume,
						table13_range)) == NULL)
			goto __error;
		if (snd_mixer_element_route_add(mixer, element1, out_bypass_accu) < 0)
			goto __error;
		ac97->me_mux_mic = element1;
		ac97->me_mux2_mic = element1;
		if (snd_mixer_element_route_add(mixer, element1, mux) < 0)
			goto __error;
	}
	/* build the line input */
	if (snd_ac97_try_volume_mix(ac97, AC97_LINE)) {
		if ((element1 = snd_ac97_build_stereo_input(mixer, ac97,
						SND_MIXER_IN_LINE,
						(snd_mixer_sw2_control_t *)snd_ac97_mute_line,
						(snd_mixer_volume1_control_t *)snd_ac97_line_volume)) == NULL)
			goto __error;
		if (snd_mixer_element_route_add(mixer, element1, out_accu) < 0)
			goto __error;
		ac97->me_mux_line = element1;
		if (snd_mixer_element_route_add(mixer, element1, mux) < 0)
			goto __error;
	}
	/* build the CD input */
	if (snd_ac97_try_volume_mix(ac97, AC97_CD)) {
		if ((element1 = snd_ac97_build_stereo_input(mixer, ac97,
						SND_MIXER_IN_CD,
						(snd_mixer_sw2_control_t *)snd_ac97_mute_cd,
						(snd_mixer_volume1_control_t *)snd_ac97_cd_volume)) == NULL)
			goto __error;
		if (snd_mixer_element_route_add(mixer, element1, out_accu) < 0)
			goto __error;
		ac97->me_mux_cd = element1;
		if (snd_mixer_element_route_add(mixer, element1, mux) < 0)
			goto __error;
	}
	/* build the VIDEO input */
	if (snd_ac97_try_volume_mix(ac97, AC97_VIDEO)) {
		if ((element1 = snd_ac97_build_stereo_input(mixer, ac97,
						SND_MIXER_IN_VIDEO,
						(snd_mixer_sw2_control_t *)snd_ac97_mute_video,
						(snd_mixer_volume1_control_t *)snd_ac97_video_volume)) == NULL)
			goto __error;
		if (snd_mixer_element_route_add(mixer, element1, out_accu) < 0)
			goto __error;
		ac97->me_mux_video = element1;
		if (snd_mixer_element_route_add(mixer, element1, mux) < 0)
			goto __error;
	}
	/* build the AUX input */
	if (snd_ac97_try_volume_mix(ac97, AC97_AUX)) {
		if ((element1 = snd_ac97_build_stereo_input(mixer, ac97,
						SND_MIXER_IN_AUX,
						(snd_mixer_sw2_control_t *)snd_ac97_mute_aux,
						(snd_mixer_volume1_control_t *)snd_ac97_aux_volume)) == NULL)
			goto __error;
		if (snd_mixer_element_route_add(mixer, element1, out_accu) < 0)
			goto __error;
		ac97->me_mux_aux = element1;
		if (snd_mixer_element_route_add(mixer, element1, mux) < 0)
			goto __error;
	}
	/* build 3D control */
	element1 = out_accu;
	if (ac97->caps & 0x7c00) {	/* 3D enhancement */
		struct snd_mixer_element_3d_effect1_info info;
		
		memset(&info, 0, sizeof(info));
		info.effect = SND_MIXER_EFF1_SW | SND_MIXER_EFF1_SPACE | SND_MIXER_EFF1_DEPTH;
		snd_ac97_change_3d_params(ac97);
		info.max_space = info.max_depth = ac97->max_3d;
		if ((element1 = snd_mixer_lib_3d_effect1(mixer,
						SND_MIXER_GRP_EFFECT, 0,
						&info,
						(snd_mixer_3d_effect1_control_t *)snd_ac97_3d,
						ac97)) == NULL)
			goto __error;
		if (snd_mixer_element_route_add(mixer, out_accu, element1) < 0)
			goto __error;
	}
	if (out_pcm_accu) {
		if (snd_mixer_element_route_add(mixer, element1, out_pcm_accu) < 0)
			goto __error;
		element1 = out_pcm_accu;
	}
	if (snd_mixer_element_route_add(mixer, element1, out_mono_accu) < 0)
		goto __error;
	if (out_bypass_accu) {
		if (snd_mixer_element_route_add(mixer, element1, out_bypass_accu) < 0)
			goto __error;
		element1 = out_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 ((element2 = snd_mixer_lib_tone_control1(mixer, SND_MIXER_GRP_TONE_CONTROL, 0, &info, (snd_mixer_tone_control1_control_t *)snd_ac97_tone_control, ac97)) == NULL)
			goto __error;
		if (snd_mixer_element_route_add(mixer, element1, element2) < 0)
			goto __error;
		element1 = element2;
	}
	/* make a connection to input mixer */
	ac97->me_mux_mix = element1;
	if (snd_mixer_element_route_add(mixer, element1, mux) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, element1, in_mono_accu) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, in_mono_accu, mux) < 0)
		goto __error;
	/* build master volume out */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_OUT_MASTER, 0)) == 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 ((element2 = snd_mixer_lib_volume1(mixer, "Master Volume", 0, 2, tmp_stereo_range, (snd_mixer_volume1_control_t *)snd_ac97_master_volume, 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;
	if ((element3 = snd_mixer_lib_sw2(mixer, "Master Switch", 0, (snd_mixer_sw2_control_t *)snd_ac97_mute_master, 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;
	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;
	/* build headphone out */
	if (snd_ac97_try_volume_mix(ac97, AC97_HEADPHONE)) {
		if ((group = snd_mixer_lib_group(mixer, SND_MIXER_OUT_HEADPHONE, 0)) == 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 ((element2 = snd_mixer_lib_volume1(mixer, "Headphone Volume", 0, 2, tmp_stereo_range, (snd_mixer_volume1_control_t *)snd_ac97_headphone_volume, 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;
		if ((element3 = snd_mixer_lib_sw2(mixer, "Headphone Switch", 0, (snd_mixer_sw2_control_t *)snd_ac97_mute_headphone, 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;
		if ((element4 = 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, element3, element4) < 0)
			goto __error;
	}
	/* build master mono out */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_OUT_MASTER_MONO, 0)) == 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 ((element2 = snd_mixer_lib_volume1(mixer, "Master Mono Volume", 0, 1, tmp_stereo_range, (snd_mixer_volume1_control_t *)snd_ac97_master_mono_volume, ac97)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, out_mono_mux, element2) < 0)
		goto __error;
	if ((element3 = snd_mixer_lib_sw2(mixer, "Master Mono Switch", 0, (snd_mixer_sw2_control_t *)snd_ac97_mute_master_mono, 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;
	if ((element4 = 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, element3, element4) < 0)
		goto __error;

	/* PCM record */
	if ((group = snd_mixer_lib_group(mixer, SND_MIXER_GRP_IGAIN, 0)) == NULL)
		goto __error;
	if ((element2 = snd_mixer_lib_volume1(mixer, "Input Gain Volume", 0, 2, table15_range, (snd_mixer_volume1_control_t *)snd_ac97_record_gain_volume, ac97)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, element2) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, mux, element2) < 0)
		goto __error;
	if ((element3 = snd_mixer_lib_sw2(mixer, "Input Gain Switch", 0, (snd_mixer_sw2_control_t *)snd_ac97_mute_record_gain, 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;
	if ((element4 = 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, element3, element4) < 0)
		goto __error;

	/* PCM record MIC */
	if (snd_ac97_try_volume_mix(ac97, AC97_REC_GAIN_MIC)) {
		if ((group = snd_mixer_lib_group(mixer, SND_MIXER_GRP_MIC_GAIN, 0)) == NULL)
			goto __error;
		if ((element2 = snd_mixer_lib_volume1(mixer, "MIC Gain Volume", 0, 1, tmp_stereo_range, (snd_mixer_volume1_control_t *)snd_ac97_record_gain_mic_volume, ac97)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, element2) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, mux, element2) < 0)
			goto __error;
		if ((element4 = snd_mixer_lib_sw2(mixer, "MIC Gain Switch", 0, (snd_mixer_sw2_control_t *)snd_ac97_mute_record_gain_mic, ac97)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, element4) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, element2, element4) < 0)
			goto __error;
		if ((element4 = 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, element3, element4) < 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;
	snd_spin_prepare(ac97, access);
	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;

	snd_spin_lock(ac97, access, &flags);
	old = ac97->read(ac97->private_data, reg);
	ac97->write(ac97->private_data, reg, (old & mask) | val);
	snd_spin_unlock(ac97, access, &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;
	}
}

/*
 *  INIT part
 */

#ifndef LINUX_2_1
extern struct symbol_table snd_symbol_table_ac97_codec_export;
#endif

int init_module(void)
{
#ifndef LINUX_2_1
	if (register_symtab(&snd_symbol_table_ac97_codec_export) < 0)
		return -ENOMEM;
#endif
	return 0;
}

void cleanup_module(void)
{
}
