/*
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.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__
#include "driver.h"
#include "info.h"
#include "schedule.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 int snd_ac97_volume_level(snd_kmixer_t * mixer, snd_kmixer_channel_t * channel,	int left, int right);
static int snd_ac97_rec_volume_level(snd_kmixer_t * mixer, snd_kmixer_channel_t * channel,	int left, int right);

static struct {
	unsigned int id;
	char *name;
} snd_ac97_codec_ids[] = {
	{ 0x414B4D00, "Asahi Kasei AK4540" },
	{ 0x41445303, "Analog Devices AD1819A" },
	{ 0x41445340, "Analog Devices AD1881" },
	{ 0x414C4320, "Avance Logic ALC100" },
	{ 0x54524123, "Creative Labs CT1297" },
	{ 0x43525903, "Crystal CS4297" },
	{ 0x43525911, "Crystal CS4297A" },
	{ 0x43525923, "Crystal CS4298" },
	{ 0x43525931, "Crystal CS4299" },
	{ 0x4e534300, "NatSemi LM4540/3/5/6/7/8" },
	{ 0x4e534331, "NatSemi LM4549" },
	{ 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"
};
/*

 */

#define AC97_MIXS \
		(sizeof(snd_ac97_mixs)/sizeof(struct snd_stru_mixer_channel_hw))
#define AC97_PRIVATE( reg, shift, misc ) ((reg<<16)|(shift<<8)|misc)

static int snd_ac97_input_mute(snd_kmixer_t * mixer,
			       snd_kmixer_channel_t * channel,
			       unsigned int mute)
{
	ac97_t *ac97 = (ac97_t *) mixer->private_data;
	unsigned short reg, val, rval;

	reg = (unsigned short) (channel->hw.private_value >> 16);
	switch (reg) {
	case AC97_MIC:
		val = 0;
		break;
	case AC97_CD:
		val = 1;
		break;
	case AC97_VIDEO:
		val = 2;
		break;
	case AC97_AUX:
		val = 3;
		break;
	case AC97_LINE:
		val = 4;
		break;
	case AC97_MASTER:
		val = 5;
		break;
	case AC97_MASTER_MONO:
		val = 6;
		break;
	case AC97_PHONE:
		val = 7;
		break;
	default:
		return -EINVAL;
	}
	if ((mute & SND_MIX_MUTE) == 0)
		mixer->private_value |= (1 << val);
	else
		mixer->private_value &= ~(1 << val);
	val = 0xffff;
	for (reg = 0; reg < 8; reg++)
		if (mixer->private_value & (1 << reg)) {
			val = reg;
			break;
		}
	snd_mutex_lock(ac97, access);
	if ( val != 0xffff) {
		rval = ac97->read(ac97->private_data, AC97_REC_GAIN);
		rval &= ~0x8000;
		ac97->write(ac97->private_data, AC97_REC_GAIN, rval);
		ac97->write(ac97->private_data, AC97_REC_SEL, val | (val << 8));
	} else {
		rval = ac97->read(ac97->private_data, AC97_REC_GAIN);
		rval |= 0x8000;
		ac97->write(ac97->private_data, AC97_REC_GAIN, rval);
	}
	snd_mutex_unlock(ac97, access);
	if ( !(mute & SND_MIX_MUTE) )
		snd_ac97_rec_volume_level(mixer, channel, channel->input.left, channel->input.right);
	return 0;
}

static int snd_ac97_output_mute(snd_kmixer_t * mixer,
				 snd_kmixer_channel_t * channel,
				 unsigned int mute)
{
	ac97_t *ac97 = (ac97_t *) mixer->private_data;
	unsigned short reg, val, shift;

	reg = (unsigned short) (channel->hw.private_value >> 16);
	shift = (unsigned char) (channel->hw.private_value >> 8);
	snd_mutex_lock(ac97, access);
	val = ac97->read(ac97->private_data, reg);
	if (reg == AC97_MASTER_TONE) {
		if (mute & SND_MIX_MUTE) {
			val |= 0x0f << shift;
			channel->private_value |= 1 << 16;
		} else {
			val &= ~(0x0f << shift);
			val |= channel->private_value & (0x0f << shift);
			channel->private_value &= ~(1 << 16);
		}
	}
	else if( !(channel->hw.ocaps & SND_MIXER_CINFO_DCAP_JOINMUTE) )
	{
		if (mute & SND_MIX_MUTE_LEFT)
			val |= 0x8000;
		else
			val &= ~0x8000;
		if (mute & SND_MIX_MUTE_RIGHT)
			val |= 0x0080;
		else
			val &= ~0x0080;
	} else {
		if (mute & SND_MIX_MUTE)
			val |= 0x8000;
		else
			val &= ~0x8000;
	}
	ac97->write(ac97->private_data, reg, val);
	snd_mutex_unlock(ac97, access);
	return 0;
}

static int snd_ac97_volume_level(snd_kmixer_t * mixer,
				 snd_kmixer_channel_t * channel,
				 int left, int right)
{
	ac97_t *ac97 = (ac97_t *) mixer->private_data;
	unsigned short reg, val, shift;

	reg = (unsigned short) (channel->hw.private_value >> 16);
	shift = (unsigned char) (channel->hw.private_value >> 8);
	snd_mutex_lock(ac97, access);
	val = ac97->read(ac97->private_data, reg);
	if (!(channel->hw.private_value & 1)) {		/* invert */
		left = channel->hw.omax - left;
		right = channel->hw.omax - right;
	}
	val &= ~(channel->hw.omax << + 8);
	val |= left << (shift + 8);
	if (channel->hw.ocaps & SND_MIXER_CINFO_DCAP_STEREO) {
		val &= ~(channel->hw.omax << shift);
		val |= right << shift;
	}
	if (reg == AC97_MASTER_TONE) {
		channel->private_value &= 0xffff0000;
		channel->private_value |= val;
		if (channel->private_value & (1 << 16))
			val = 0x0f << shift;
	}
	ac97->write(ac97->private_data, reg, val);
	snd_mutex_unlock(ac97, access);
	return 0;
}

static int snd_ac97_rec_volume_level(snd_kmixer_t * mixer,
				  snd_kmixer_channel_t * channel,
				  int left, int right)
{
	ac97_t *ac97 = (ac97_t *) mixer->private_data;
	unsigned short reg, val = 0;
	reg = (unsigned short) (channel->hw.private_value >> 16);

	switch (reg) {
	case AC97_MIC:
		val = 0;
		break;
	case AC97_CD:
		val = 1;
		break;
	case AC97_VIDEO:
		val = 2;
		break;
	case AC97_AUX:
		val = 3;
		break;
	case AC97_LINE:
		val = 4;
		break;
	case AC97_MASTER:
		val = 5;
		break;
	case AC97_MASTER_MONO:
		val = 6;
		break;
	case AC97_PHONE:
		val = 7;
		break;
	}
	if ((mixer->private_value & (1 << val)) == 0 )
		return 0;
			
	snd_mutex_lock(ac97, access);
	reg = AC97_REC_GAIN;
	val = ac97->read(ac97->private_data, reg);
	val &= ~channel->hw.imax;
	val |= left;

	if (channel->hw.ocaps & SND_MIXER_CINFO_DCAP_STEREO) {
		val &= ~(channel->hw.imax << 8);
		val |= right << 8;
	} else {
		val &= ~(channel->hw.imax << 8);
		val |= left << 8;
	}
	ac97->write(ac97->private_data, reg, val);
	snd_mutex_unlock(ac97, access);
	return 0;
}

static int snd_ac97_volume(snd_kmixer_t * mixer,
			   snd_kmixer_channel_t * channel,
			   unsigned int type, int left, int right)
{
	switch (type) {
	case SND_MIX_VOL_OUTPUT:
		return snd_ac97_volume_level(mixer, channel, left, right);
	case SND_MIX_VOL_INPUT:
		return snd_ac97_rec_volume_level(mixer, channel, left, right);
	}
	return -EINVAL;
}

static int snd_ac97_control(snd_kmixer_t * mixer,
			    snd_kmixer_channel_t * channel,
			    unsigned int ctrl, long val)
{
	switch (ctrl) {
	case SND_MIX_CTRL_MUTE_OUT:
		return snd_ac97_output_mute(mixer, channel, val);
	case SND_MIX_CTRL_MUTE_IN:
		return snd_ac97_input_mute(mixer, channel, val);
	}
	return -EINVAL;
}

static struct snd_stru_mixer_channel_hw snd_ac97_mixs[] =
{
	{
		SND_MIXER_PRI_MASTER,	/* priority */
		SND_MIXER_PRI_PARENT,	/* parent priority */
		SND_MIXER_ID_MASTER,	/* device name */
		SND_MIXER_OSS_VOLUME,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_HWMUTE | SND_MIXER_CINFO_DCAP_JOINMUTE,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_RECORDBYMUTE |
		SND_MIXER_CINFO_DCAP_HWMUTE | SND_MIXER_CINFO_DCAP_JOINMUTE |
		SND_MIXER_CINFO_DCAP_EXCLUSIVE | SND_MIXER_CINFO_DCAP_VOLUME,
		0, 63,			/* omin, omax value */
		-9450, 0,		/* omin, omax dB */
		0, 15,			/* imin, iomax value */
		0, 2250,		/* imin, imax dB */
		snd_ac97_control,	/* set control */
		snd_ac97_volume,	/* set volume level*/
		AC97_PRIVATE(AC97_MASTER, 0, 0)
	},
	{
		SND_MIXER_PRI_HEADPHONE,/* priority */
		SND_MIXER_PRI_PARENT,	/* parent priority */
		SND_MIXER_ID_HEADPHONE,	/* device name */
		SND_MIXER_OSS_LINE1,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_HWMUTE | SND_MIXER_CINFO_DCAP_JOINMUTE,
		/* Input capabilities */
		0,
		0, 63,			/* omin, omax value */
		-9450, 0,		/* omin, omax dB */
        0, 0,			/* imin, imax value */
        0, 0,			/* imin, imax dB */
		snd_ac97_control,	/* set control */
		snd_ac97_volume,	/* set volume level*/
		AC97_PRIVATE(AC97_HEADPHONE, 0, 0)
	},
	{
		SND_MIXER_PRI_MASTER_MONO, /* priority */
		SND_MIXER_PRI_PARENT,	/* parent priority */
		SND_MIXER_ID_MASTER_MONO, /* device name */
		SND_MIXER_OSS_LINE2,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_VOLUME | SND_MIXER_CINFO_DCAP_HWMUTE,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_HWMUTE | SND_MIXER_CINFO_DCAP_EXCLUSIVE |
		SND_MIXER_CINFO_DCAP_VOLUME,
		0, 63,			/* omin, omax value */
		-9450, 0,		/* omin, omax dB */
        0, 15,			/* imin, imax value */
        0, 2250,		/* imin, imax dB */
		snd_ac97_control,	/* set control */
		snd_ac97_volume,	/* set volume level*/
		AC97_PRIVATE(AC97_MASTER_MONO, 0, 0)
	},
	{
		SND_MIXER_PRI_BASS,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_BASS,	/* device name */
		SND_MIXER_OSS_BASS,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_EFFECT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_VOLUME | SND_MIXER_CINFO_DCAP_HWMUTE,
		/* Input capabilities */
		0,
		0, 14,			/* omin, omax value */
		-1050, 1050,		/* omin, omax dB */
        0, 0,			/* imin, imax value */
        0, 0,			/* imin, imax dB */
		snd_ac97_control,	/* set control */
		snd_ac97_volume,	/* set volume level*/
		AC97_PRIVATE(AC97_MASTER_TONE, 8, 0)
	},
	{
		SND_MIXER_PRI_TREBLE,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_TREBLE,	/* device name */
		SND_MIXER_OSS_TREBLE,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_EFFECT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_VOLUME | SND_MIXER_CINFO_DCAP_HWMUTE,
		/* Input capabilities */
		0,
		0, 14,			/* omin, omax value */
		-1050, 1050,		/* omin, omax dB */
        0, 0,			/* imin, imax value */
        0, 0,			/* imin, imax dB */
		snd_ac97_control,	/* set control */
		snd_ac97_volume,	/* set volume level*/
		AC97_PRIVATE(AC97_MASTER_TONE, 0, 0)
	},
	{
		SND_MIXER_PRI_SPEAKER,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_SPEAKER,	/* device name */
		SND_MIXER_OSS_SPEAKER,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_EXTINPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_VOLUME | SND_MIXER_CINFO_DCAP_HWMUTE,
		/* Input capabilities */
		0,
		0, 15,			/* omin, omax value */
		-4500, 0,		/* omin, omax dB */
        0, 0,			/* imin, imax value */
        0, 0,			/* imin, imax dB */
		snd_ac97_control,	/* set control */
		snd_ac97_volume,	/* set volume level*/
		AC97_PRIVATE(AC97_PC_BEEP, 1, 0)
	},
	{
		SND_MIXER_PRI_PHONE,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_PHONE,	/* device name */
		SND_MIXER_OSS_PHONEOUT,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT |
		SND_MIXER_CINFO_CAP_EXTINPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_VOLUME | SND_MIXER_CINFO_DCAP_HWMUTE,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_HWMUTE | SND_MIXER_CINFO_DCAP_EXCLUSIVE | 
		SND_MIXER_CINFO_DCAP_VOLUME,
		0, 31,			/* omin, omax value */
		-3450, 1200,		/* omin, omax dB */
        0, 15,			/* imin, imax value */
        0, 2250,			/* imin, imax dB */
		snd_ac97_control,	/* set control */
		snd_ac97_volume,	/* set volume level*/
		AC97_PRIVATE(AC97_PHONE, 0, 0)
	},
	{
		SND_MIXER_PRI_MIC,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_MIC,	/* device name */
		SND_MIXER_OSS_MIC,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT |
		SND_MIXER_CINFO_CAP_EXTINPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_VOLUME | SND_MIXER_CINFO_DCAP_HWMUTE,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_HWMUTE | SND_MIXER_CINFO_DCAP_EXCLUSIVE | 
		SND_MIXER_CINFO_DCAP_VOLUME,
		0, 31,			/* omin, omax value */
		-3450, 1200,		/* omin, omax dB */
        0, 15,			/* imin, imax value */
        0, 2250,			/* imin, imax dB */
		snd_ac97_control,	/* set control */
		snd_ac97_volume,	/* set volume level*/
		AC97_PRIVATE(AC97_MIC, 0, 0)
	},
	{
		SND_MIXER_PRI_LINE,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_LINE,	/* device name */
		SND_MIXER_OSS_LINE,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT |
		SND_MIXER_CINFO_CAP_EXTINPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_HWMUTE | SND_MIXER_CINFO_DCAP_JOINMUTE,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_EXCLUSIVE |
		SND_MIXER_CINFO_DCAP_HWMUTE | SND_MIXER_CINFO_DCAP_JOINMUTE | 
		SND_MIXER_CINFO_DCAP_VOLUME,
		0, 31,			/* omin, omax value */
		-3450, 1200,		/* omin, omax dB */
        0, 15,			/* imin, imax value */
        0, 2250,			/* imin, imax dB */
		snd_ac97_control,	/* set control */
		snd_ac97_volume,	/* set volume level*/
		AC97_PRIVATE(AC97_LINE, 0, 0)
	},
	{
		SND_MIXER_PRI_CD,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_CD,	/* device name */
		SND_MIXER_OSS_CD,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT |
		SND_MIXER_CINFO_CAP_EXTINPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_HWMUTE | SND_MIXER_CINFO_DCAP_JOINMUTE,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_EXCLUSIVE |
		SND_MIXER_CINFO_DCAP_HWMUTE | SND_MIXER_CINFO_DCAP_JOINMUTE | 
		SND_MIXER_CINFO_DCAP_VOLUME,
		0, 31,			/* omin, omax value */
		-3450, 1200,	/* omin, omax dB */
        0, 15,			/* imin, imax value */
        0, 2250,			/* imin, imax dB */
		snd_ac97_control,	/* set control */
		snd_ac97_volume,	/* set volume level*/
		AC97_PRIVATE(AC97_CD, 0, 0)
	},
	{
		SND_MIXER_PRI_VIDEO,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_VIDEO,	/* device name */
		SND_MIXER_OSS_VIDEO,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT |
		SND_MIXER_CINFO_CAP_EXTINPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_HWMUTE | SND_MIXER_CINFO_DCAP_JOINMUTE,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_EXCLUSIVE |
		SND_MIXER_CINFO_DCAP_HWMUTE | SND_MIXER_CINFO_DCAP_JOINMUTE | 
		SND_MIXER_CINFO_DCAP_VOLUME,
		0, 31,			/* omin, omax value */
		-3450, 1200,	/* omin, omax dB */
        0, 15,			/* imin, imax value */
        0, 2250,			/* imin, imax dB */
		snd_ac97_control,	/* set control */
		snd_ac97_volume,	/* set volume level*/
		AC97_PRIVATE(AC97_VIDEO, 0, 0)
	},
	{
		SND_MIXER_PRI_AUXA,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_AUXA,	/* device name */
		SND_MIXER_OSS_LINE3,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_INPUT |
		SND_MIXER_CINFO_CAP_EXTINPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_HWMUTE | SND_MIXER_CINFO_DCAP_JOINMUTE,
		/* Input capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_EXCLUSIVE |
		SND_MIXER_CINFO_DCAP_HWMUTE | SND_MIXER_CINFO_DCAP_JOINMUTE | 
		SND_MIXER_CINFO_DCAP_VOLUME,
		0, 31,			/* omin, omax value */
		-3450, 1200,		/* omin, omax dB */
        0, 15,			/* imin, imax value */
        0, 2250,			/* imin, imax dB */
		snd_ac97_control,	/* set control */
		snd_ac97_volume,	/* set volume level*/
		AC97_PRIVATE(AC97_AUX, 0, 0)
	},
	{
		SND_MIXER_PRI_PCM,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_PCM,	/* device name */
		SND_MIXER_OSS_PCM,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_HWMUTE | SND_MIXER_CINFO_DCAP_JOINMUTE,
		/* Input capabilities */
		0,
		0, 31,			/* omin, omax value */
		-3450, 1200,		/* omin, omax dB */
        0, 0,			/* imin, imax value */
        0, 0,			/* imin, imax dB */
		snd_ac97_control,	/* set control */
		snd_ac97_volume,	/* set volume level*/
		AC97_PRIVATE(AC97_PCM, 0, 0)
	},
	{
		SND_MIXER_PRI_MIC_GAIN,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_MIC_GAIN,	/* device name */
		SND_MIXER_OSS_MONITOR,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_EFFECT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_VOLUME | SND_MIXER_CINFO_DCAP_HWMUTE,
		/* Input capabilities */
		0,
		0, 15,			/* omin, omax value */
		0, 2250,		/* omin, omax dB */
                0, 0,			/* imin, imax value */
                0, 0,			/* imin, imax dB */
		snd_ac97_control,	/* set control */
		snd_ac97_volume,	/* set volume level*/
		AC97_PRIVATE(AC97_REC_GAIN_MIC, 0, 1)
	},
	{
		SND_MIXER_PRI_3D_CENTER,/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_3D_CENTER,	/* device name */
		SND_MIXER_OSS_DIGITAL1,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_EFFECT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_VOLUME,
		/* Input capabilities */
		0,
		0, 15,			/* omin, omax value */
		0, 0,			/* omin, omax dB */
        0, 0,			/* imin, imax value */
        0, 0,			/* imin, imax dB */
		0,			/* set control */
		snd_ac97_volume,	/* set volume level*/
		AC97_PRIVATE(AC97_3D_CONTROL, 8, 0)
	},
	{
		SND_MIXER_PRI_3D_DEPTH,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_3D_DEPTH,	/* device name */
		SND_MIXER_OSS_DIGITAL2,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_EFFECT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_VOLUME,
		/* Input capabilities */
		0,
		0, 15,			/* omin, omax value */
		0, 0,			/* omin, omax dB */
        0, 0,			/* imin, imax value */
        0, 0,			/* imin, imax dB */
		0,			/* set control */
		snd_ac97_volume,	/* set volume level*/
		AC97_PRIVATE(AC97_3D_CONTROL, 0, 0)
	},
	{
		SND_MIXER_PRI_PCM,			/* priority */
		SND_MIXER_PRI_MASTER,		/* parent priority */
		SND_MIXER_ID_SURROUND,		/* device name */
		SND_MIXER_OSS_ALTPCM,		/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_HWMUTE,
		/* Input capabilities */
		0,
		0, 63,			/* omin, omax value */
		-9450, 0,		/* omin, omax dB */
        0, 0,			/* imin, imax value */
        0, 0,			/* imin, imax dB */
		snd_ac97_control,	/* set control */
		snd_ac97_volume,	/* set volume level*/
		AC97_PRIVATE(AC97_XA_SURROUND_VOL, 0, 0)
	},

};

/*

 */

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

	uswitch->type = SND_SW_TYPE_BOOLEAN;
	snd_mutex_lock(ac97, access);
	val = ac97->read(ac97->private_data, AC97_MIC);
	uswitch->value.enable = val & 0x0040 ? 1 : 0;
	snd_mutex_unlock(ac97, access);
	return 0;
}

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

	if (uswitch->type != SND_SW_TYPE_BOOLEAN)
		return -EINVAL;
	snd_mutex_lock(ac97, access);
	val = ac97->read(ac97->private_data, AC97_MIC);
	val &= ~0x0040;
	val |= uswitch->value.enable ? 0x0040 : 0x0000;
	if (uswitch->value.enable) {
		ac97->mic_channel->hw.omin_dB = -1450;
		ac97->mic_channel->hw.omax_dB = 2000;
	} else {
		ac97->mic_channel->hw.omin_dB = -3450;
		ac97->mic_channel->hw.omax_dB = 0;
	}
	ac97->write(ac97->private_data, AC97_MIC, val);
	snd_mutex_unlock(ac97, access);
	return 0;
}

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

	uswitch->type = SND_SW_TYPE_BOOLEAN;
	snd_mutex_lock(ac97, access);
	val = ac97->read(ac97->private_data, AC97_GENERAL_PURPOSE);
	uswitch->value.enable = val & kswitch->private_value ? 1 : 0;
	snd_mutex_unlock(ac97, access);
	return 0;
}

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

	if (uswitch->type != SND_SW_TYPE_BOOLEAN)
		return -EINVAL;
	snd_mutex_lock(ac97, access);
	val = ac97->read(ac97->private_data, AC97_GENERAL_PURPOSE);
	val &= ~kswitch->private_value;
	val |= uswitch->value.enable ? kswitch->private_value : 0x0000;
	ac97->write(ac97->private_data, AC97_GENERAL_PURPOSE, val);
	snd_mutex_unlock(ac97, access);
	return 0;
}

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

static snd_kswitch_t snd_ac97_switches[] =
{
	{
		SND_MIXER_SW_MIC_GAIN,
		(snd_get_switch_t *)snd_ac97_get_mic_switch,
		(snd_set_switch_t *)snd_ac97_set_mic_switch,
		0,
		NULL,
		NULL
	},
	{
		"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_3D,
		(snd_get_switch_t *)snd_ac97_get_switch,
		(snd_set_switch_t *)snd_ac97_set_switch,
		0x2000,
		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_mutex_destroy(ac97, access);
		snd_ac97_proc_done(ac97);
		if (ac97->private_free)
			ac97->private_free(ac97);
	}
}

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

	reg = (unsigned short) (mix->private_value >> 16);
	switch (reg) {
	case AC97_MASTER_TONE:
		return ac97->caps & 0x04 ? 1 : 0;
	case AC97_HEADPHONE:
		return ac97->caps & 0x10 ? 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 int snd_ac97_try_switch(ac97_t * ac97, snd_kswitch_t *kswitch)
{
	unsigned short val;
	
	switch (kswitch->private_value) {
		case AC97_GP_POP:
		case AC97_GP_MIX:
		case AC97_GP_MS:
		case AC97_GP_LPBK:
			ac97->write(ac97->private_data, AC97_GENERAL_PURPOSE, kswitch->private_value);
			val = ac97->read(ac97->private_data, AC97_GENERAL_PURPOSE );
			if ( val & kswitch->private_value ) {
				ac97->write(ac97->private_data, AC97_GENERAL_PURPOSE, 0);
				return 1;
			}
			return 0;
		case AC97_GP_ST:
			return ac97->caps & 0x08 ? 1 : 0;
		case AC97_GP_3D:
			if ( ac97->caps & 0x7c00 ) {
				val = ac97->read(ac97->private_data, AC97_3D_CONTROL);
				/* if nonzero - fixed and we can't set it */
				return val == 0;
			} 
			return 0;
		case AC97_GP_LD:
			return ac97->caps & 0x20 ? 1 : 0;
	}
	return 1;		/* success, useable */
}

static void snd_ac97_change_channel(ac97_t * ac97,
				    snd_kmixer_channel_t * channel)
{
	unsigned short reg, val, val1;

	reg = (unsigned short) (channel->hw.private_value >> 16);
	/* ok. some volume registers have MSB optional.. try test it */
	if (reg == AC97_MASTER ||
	    reg == AC97_HEADPHONE ||
	    reg == AC97_MASTER_MONO ||
		reg == AC97_XA_SURROUND_VOL) {
		val = 0x8000 | 0x0020;
		ac97->write(ac97->private_data, reg, val);
		val1 = ac97->read(ac97->private_data, reg);
		if (val != val1) {
			channel->hw.omax = 31;
			channel->hw.omin_dB = -4650;
		}
		/* reset volume to zero */
		ac97->write(ac97->private_data, reg, val = 0x8000);
	}
	/* next register should have some bits stripped */
	if (reg == AC97_MASTER_TONE) {
		val = 0x0707;
		ac97->write(ac97->private_data, reg, val);
		val = ac97->read(ac97->private_data, reg);
		if (val == 0x0606) {
			channel->hw.omax = 7;
			channel->hw.private_value |= 1 << 8;	/* set shift */
		}
		ac97->write(ac97->private_data, reg, 0x0f0f);
	}
}

snd_kmixer_t *snd_ac97_mixer(snd_card_t * card, ac97_t * ac97)
{
	int idx, mixs_count;
	snd_kmixer_t *mixer;
	snd_kmixer_channel_t *channel;
	struct snd_stru_mixer_channel_hw *mixs;

	if (!card || !ac97)
		return NULL;
	snd_mutex_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 */
	snd_delay(10);
	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->xa_caps = ac97->read(ac97->private_data, AC97_XA_ID);
	ac97->write(ac97->private_data, AC97_POWERDOWN, 0);	/* nothing should be in powerdown mode */
	snd_delay(10);
	mixs = snd_ac97_mixs;
	mixs_count = AC97_MIXS;
	for (idx = 0; idx < mixs_count; idx++) {
		if (snd_ac97_try_volume_mix(ac97, &mixs[idx])) {
			channel = snd_mixer_new_channel(mixer, &mixs[idx]);
			if (!channel) {
				snd_mutex_destroy(ac97, access);
				snd_mixer_free(mixer);
				return NULL;
			}
			snd_ac97_change_channel(ac97, channel);
			if (channel->hw.priority == SND_MIXER_PRI_MIC)
				ac97->mic_channel = channel;
		}
	}
	for (idx = 0; idx < AC97_SWITCHES; idx++) {
		if (snd_ac97_try_switch(ac97, &snd_ac97_switches[idx]))
			snd_mixer_switch_new(mixer, &snd_ac97_switches[idx], ac97);
	}
	mixer->hw.caps = 0;
	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, (unsigned char) (ac97->id >> 24), (unsigned char) (ac97->id >> 16), (unsigned char) (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);
			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;

	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");
}

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
 */

