/*
 * BeOS Driver for Intel ICH AC'97 Link interface
 *
 * Copyright (c) 2002, Marcus Overhagen <marcus@overhagen.de>
 *
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice, 
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation 
 *   and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
#include <OS.h>
#include <stdio.h>
#include <malloc.h>
#include <MediaDefs.h>
#include "ac97.h"
#include "debug.h"

#define B_UTF8_REGISTERED	"\xC2\xAE"

bool ac97_reg_is_valid(ac97_dev *dev, uint8 reg);
void ac97_amp_enable(ac97_dev *dev, bool onoff);
void ac97_dump_capabilities(ac97_dev *dev);
void ac97_detect_capabilities(ac97_dev *dev);
void ac97_detect_rates(ac97_dev *dev);
void ac97_update_register_cache(ac97_dev *dev);

const char * stereo_enhancement_technique[] =
{
	"No 3D Stereo Enhancement",
	"Analog Devices",
	"Creative Technology",
	"National Semiconductor",
	"Yamaha",
	"BBE Sound",
	"Crystal Semiconductor",
	"Qsound Labs",
	"Spatializer Audio Laboratories",
	"SRS Labs",
	"Platform Tech",
	"AKM Semiconductor",
	"Aureal",
	"Aztech Labs",
	"Binaura",
	"ESS Technology",
	"Harman International",
	"Nvidea",
	"Philips"
	"Texas Instruments",
	"VLSI Technology",
	"TriTech",
	"Realtek",
	"Samsung",
	"Wolfson Microelectronics",
	"Delta Integration",
	"SigmaTel",
	"KS Waves",
	"Rockwell",
	"Unknown (29)",
	"Unknown (30)",
	"Unknown (31)"
};

void default_init(ac97_dev *dev);
void ad1819_init(ac97_dev *dev);
void ad1881_init(ac97_dev *dev);
void ad1885_init(ac97_dev *dev);
void ad1886_init(ac97_dev *dev);
void ad1980_init(ac97_dev *dev);
void alc650_init(ac97_dev *dev);
void stac9708_init(ac97_dev *dev);
void stac9721_init(ac97_dev *dev);
void stac9744_init(ac97_dev *dev);
void stac9756_init(ac97_dev *dev);
void tr28028_init(ac97_dev *dev);
void wm9701_init(ac97_dev *dev);
void wm9703_init(ac97_dev *dev);
void wm9704_init(ac97_dev *dev);

bool ad1819_set_rate(ac97_dev *dev, uint8 reg, uint32 rate);
bool ad1819_get_rate(ac97_dev *dev, uint8 reg, uint32 *rate);

typedef struct
{
	uint32 id;
	uint32 mask;
	codec_init init;
	const char *info;
} codec_table;

codec_table codecs[] = 
{
	{ CODEC_ID_AD1819,	0xffffffff, ad1819_init,	"Analog Devices AD1819A, AD1819B SoundPort"B_UTF8_REGISTERED },
	{ CODEC_ID_AD1881,	0xffffffff, ad1881_init,	"Analog Devices AD1881 SoundMAX"B_UTF8_REGISTERED },
	{ CODEC_ID_AD1881A,	0xffffffff, ad1881_init,	"Analog Devices AD1881A SoundMAX"B_UTF8_REGISTERED },
	{ CODEC_ID_AD1885,	0xffffffff, ad1885_init,	"Analog Devices AD1885 SoundMAX"B_UTF8_REGISTERED },
	{ CODEC_ID_AD1886,	0xffffffff, ad1886_init,	"Analog Devices AD1886 SoundMAX"B_UTF8_REGISTERED },
	{ CODEC_ID_AD1886A,	0xffffffff, ad1881_init,	"Analog Devices AD1886A SoundMAX"B_UTF8_REGISTERED },
	{ CODEC_ID_AD1887,	0xffffffff, ad1881_init,	"Analog Devices AD1887 SoundMAX"B_UTF8_REGISTERED },
	{ CODEC_ID_AD1980,	0xffffffff, ad1980_init,	"Analog Devices AD1980 SoundMAX"B_UTF8_REGISTERED },
	{ 0x41445371,		0xffffffff, default_init,	"Analog Devices 0x41445371 (???)" },
	{ 0x41445372,		0xffffffff, default_init,	"Analog Devices AD1981A SoundMAX"B_UTF8_REGISTERED },
	{ CODEC_ID_AD1981B,	0xffffffff, default_init,	"Analog Devices AD1981B SoundMAX"B_UTF8_REGISTERED },
	{ CODEC_ID_AD1985,	0xffffffff, default_init,	"Analog Devices AD1985 SoundMAX"B_UTF8_REGISTERED },
	{ CODEC_ID_AK4540,	0xffffffff, default_init,	"Asahi Kasei AK4540" },
	{ CODEC_ID_AK4542,	0xffffffff, default_init,	"Asahi Kasei AK4542" },
	{ 0x414b4d02,		0xffffffff, default_init,	"Asahi Kasei AK4543" },
	{ 0x414c4320,		0xfffffff0, default_init,	"Avance Logic (Realtek) ALC100/ALC100P, RL5383/RL5522" },
	{ 0x414c4730,		0xffffffff, default_init,	"Avance Logic (Realtek) ALC101" },
	{ CODEC_ID_ALC201A,	0xffffffff, default_init,	"Avance Logic (Realtek) ALC200/ALC200A, ALC201/ALC201A" }, /* 0x4710 = ALC201A */
	{ 0x414c4720,		0xffffffff, alc650_init,	"Avance Logic (Realtek) ALC650" }, /* 0x4720 = ALC650 */
	{ 0x414c4740,		0xffffffff, default_init,	"Avance Logic (Realtek) ALC202/ALC202A" },
	{ 0x434d4941,		0xffffffff, default_init,	"C-Media CMI9738" },
	{ 0x434d4961,		0xffffffff, default_init,	"C-Media CMI9739" },
	{ 0x43525900,		0xffffffff, default_init,	"Cirrus Logic CS4297" },
	{ 0x43525903,		0xffffffff, default_init,	"Cirrus Logic CS4297" },
	{ 0x43525913,		0xffffffff, default_init,	"Cirrus Logic CS4297A" },
	{ 0x43525914,		0xffffffff, default_init,	"Cirrus Logic CS4297B" },
	{ 0x43525923,		0xffffffff, default_init,	"Cirrus Logic CS4294C" },
	{ 0x4352592b,		0xffffffff, default_init,	"Cirrus Logic CS4298C" },
	{ CODEC_ID_CS4299A,	0xffffffff, default_init,	"Cirrus Logic CS4299A" },
	{ CODEC_ID_CS4299C,	0xffffffff, default_init,	"Cirrus Logic CS4299C" },
	{ CODEC_ID_CS4299D,	0xffffffff, default_init,	"Cirrus Logic CS4299D" },
	{ 0x43525941,		0xffffffff, default_init,	"Cirrus Logic CS4201A" },
	{ 0x43525951,		0xffffffff, default_init,	"Cirrus Logic CS4205A" },
	{ 0x43525961,		0xffffffff, default_init,	"Cirrus Logic CS4291A" },
	{ 0x45838308,		0xffffffff, default_init,	"ESS Technology ES1921" },
	{ 0x49434511,		0xffffffff, default_init,	"ICEnsemble ICE1232" },
	{ 0x4e534331,		0xffffffff, default_init,	"National Semiconductor LM4549" },
	{ CODEC_ID_STAC9700,0xffffffff, default_init,	"SigmaTel STAC9700/9783/9784" },
	{ CODEC_ID_STAC9704,0xffffffff, default_init,	"SigmaTel STAC9701/03, STAC9704/07, STAC9705 (???)" },
	{ CODEC_ID_STAC9705,0xffffffff, default_init,	"SigmaTel STAC9704 (???)" },
	{ CODEC_ID_STAC9708,0xffffffff, stac9708_init,	"SigmaTel STAC9708/9711" },
	{ CODEC_ID_STAC9721,0xffffffff, stac9721_init,	"SigmaTel STAC9721/9723" },
	{ CODEC_ID_STAC9744,0xffffffff, stac9744_init,	"SigmaTel STAC9744" },
	{ CODEC_ID_STAC9752,0xffffffff, default_init,	"SigmaTel STAC9752/53" },
	{ CODEC_ID_STAC9756,0xffffffff, stac9756_init,	"SigmaTel STAC9756/9757" },
	{ CODEC_ID_STAC9766,0xffffffff, default_init,	"SigmaTel STAC9766/67" },
	{ 0x53494c22,		0xffffffff, default_init,	"Silicon Laboratory Si3036" },
	{ 0x53494c23,		0xffffffff, default_init,	"Silicon Laboratory Si3038" },
	{ 0x54524103,		0xffffffff, default_init,	"TriTech TR?????" },
	{ 0x54524106,		0xffffffff, default_init,	"TriTech TR28026" },
	{ 0x54524108,		0xffffffff, tr28028_init,	"TriTech TR28028" },
	{ 0x54524123,		0xffffffff, default_init,	"TriTech TR28602" },
	{ 0x574d4c00,		0xffffffff, wm9701_init,	"Wolfson WM9701A" },
	{ 0x574d4c03,		0xffffffff, wm9703_init,	"Wolfson WM9703/9704" },
	{ 0x574d4c04,		0xffffffff, wm9704_init,	"Wolfson WM9704 (quad)" },	
	/* Vendors only: */
	{ 0x41445300,		0xffffff00, default_init,	"Analog Devices" },
	{ 0x414b4d00,		0xffffff00, default_init,	"Asahi Kasei" },
	{ 0x414c4700,		0xffffff00, default_init,	"Avance Logic (Realtek)" },
	{ 0x434d4900,		0xffffff00, default_init,	"C-Media" },
	{ 0x43525900,		0xffffff00, default_init,	"Cirrus Logic" },
	{ 0x45838300,		0xffffff00, default_init,	"ESS Technology" },
	{ 0x49434500,		0xffffff00, default_init,	"ICEnsemble" },
	{ 0x4e534300,		0xffffff00, default_init,	"National Semiconductor" },
	{ 0x83847600,		0xffffff00, default_init,	"SigmaTel" },
	{ 0x53494c00,		0xffffff00, default_init,	"Silicon Laboratory" },
	{ 0x54524100,		0xffffff00, default_init,	"TriTech" },
	{ 0x574d4c00,		0xffffff00, default_init,	"Wolfson" },
	{ 0x00000000,		0x00000000, default_init,	"Unknown" } /* must be last one, matches every codec */
};

codec_table *find_codec_table(uint32 codecid);

codec_table *
find_codec_table(uint32 codecid)
{
	codec_table *codec;
	for (codec = codecs; codec->id; codec++)
		if ((codec->id & codec->mask) == (codecid & codec->mask))
			break;
	return codec;
}

void
ac97_attach(ac97_dev **_dev, codec_reg_read reg_read, codec_reg_write reg_write, void *cookie)
{
	ac97_dev *dev;
	codec_table *codec;
	
	*_dev = dev = (ac97_dev *) malloc(sizeof(ac97_dev));
	dev->cookie = cookie;
	dev->reg_read = reg_read;
	dev->reg_write = reg_write;
	dev->codec_id = (reg_read(cookie, AC97_VENDOR_ID1) << 16) | reg_read(cookie, AC97_VENDOR_ID2);
	codec = find_codec_table(dev->codec_id);
	dev->codec_info = codec->info;
	dev->init = codec->init;
	dev->set_rate = 0;
	dev->get_rate = 0;
	dev->clock = 48000; /* default clock on non-broken motherboards */
	dev->min_vsr = 0x0001;	
	dev->max_vsr = 0xffff;
	dev->reversed_eamp_polarity = false;

	/* setup register cache */
	ac97_update_register_cache(dev);

	/* reset the codec */	
	LOG(("codec reset\n"));
	ac97_reg_uncached_write(dev, AC97_RESET, 0x0000);
	snooze(50000); // 50 ms

	dev->codec_3d_stereo_enhancement = stereo_enhancement_technique[(ac97_reg_cached_read(dev, AC97_RESET) >> 10) & 31];
	dev->capabilities = 0;

	ac97_reg_update_bits(dev, AC97_EXTENDED_STAT_CTRL, 1, 1); // enable variable rate audio

	ac97_detect_capabilities(dev);

	dev->init(dev);
	ac97_amp_enable(dev, true);
	
	/* set mixer defaults, enabled Line-out sources are PCM-out, CD-in, Line-in */
	ac97_reg_update(dev, AC97_CENTER_LFE_VOLUME, 0x0000);	/* set LFE & center volume 0dB */
	ac97_reg_update(dev, AC97_SURR_VOLUME, 0x0000);			/* set surround volume 0dB */
	ac97_reg_update(dev, AC97_MASTER_VOLUME, 0x0000);		/* set master output 0dB */
	ac97_reg_update(dev, AC97_AUX_OUT_VOLUME, 0x0000);		/* set aux output 0dB */
	ac97_reg_update(dev, AC97_MONO_VOLUME, 0x0000);			/* set mono output 0dB */
	ac97_reg_update(dev, AC97_PCM_OUT_VOLUME, 0x0808);		/* enable pcm-out */
	ac97_reg_update(dev, AC97_CD_VOLUME, 0x0808);			/* enable cd-in */
	ac97_reg_update(dev, AC97_LINE_IN_VOLUME, 0x0808);		/* enable line-in */
	
	ac97_dump_capabilities(dev);
}

void
ac97_detach(ac97_dev *dev)
{
	/* Mute everything */
	ac97_reg_update_bits(dev, AC97_CENTER_LFE_VOLUME, 0x8000, 0x8000);
	ac97_reg_update_bits(dev, AC97_SURR_VOLUME, 0x8000, 0x8000);
	ac97_reg_update_bits(dev, AC97_MASTER_VOLUME, 0x8000, 0x8000);
	ac97_reg_update_bits(dev, AC97_AUX_OUT_VOLUME, 0x8000, 0x8000);
	ac97_reg_update_bits(dev, AC97_MONO_VOLUME, 0x8000, 0x8000);
	ac97_reg_update_bits(dev, AC97_PCM_OUT_VOLUME, 0x8000, 0x8000);
	ac97_reg_update_bits(dev, AC97_CD_VOLUME, 0x8000, 0x8000);
	ac97_reg_update_bits(dev, AC97_LINE_IN_VOLUME, 0x8000, 0x8000);

	free(dev);
}

void
ac97_suspend(ac97_dev *dev)
{
	ac97_amp_enable(dev, false);
}

void
ac97_resume(ac97_dev *dev)
{
	ac97_amp_enable(dev, true);
}

void
ac97_reg_cached_write(ac97_dev *dev, uint8 reg, uint16 value)
{
	if (!ac97_reg_is_valid(dev, reg))
		return;
	dev->reg_write(dev->cookie, reg, value);
	dev->reg_cache[reg] = value;
}

uint16
ac97_reg_cached_read(ac97_dev *dev, uint8 reg)
{
	if (!ac97_reg_is_valid(dev, reg))
		return 0;
	return dev->reg_cache[reg];
}

void
ac97_reg_uncached_write(ac97_dev *dev, uint8 reg, uint16 value)
{
	if (!ac97_reg_is_valid(dev, reg))
		return;
	dev->reg_write(dev->cookie, reg, value);
}

uint16
ac97_reg_uncached_read(ac97_dev *dev, uint8 reg)
{
	if (!ac97_reg_is_valid(dev, reg))
		return 0;
	return dev->reg_read(dev->cookie, reg);
}

bool
ac97_reg_update(ac97_dev *dev, uint8 reg, uint16 value)
{
	if (!ac97_reg_is_valid(dev, reg))
		return false;
	if (ac97_reg_cached_read(dev, reg) == value)
		return false;
	ac97_reg_cached_write(dev, reg, value);
	return true;
}

bool
ac97_reg_update_bits(ac97_dev *dev, uint8 reg, uint16 mask, uint16 value)
{
	uint16 old;
	if (!ac97_reg_is_valid(dev, reg))
		return false;
	old = ac97_reg_cached_read(dev, reg);
	value &= mask;
	value |= (old & ~mask);
	if (old == value)
		return false;
	ac97_reg_cached_write(dev, reg, value);
	return true;
}

void
ac97_update_register_cache(ac97_dev *dev)
{
	int reg;
	for (reg = 0; reg <= 0x7e; reg += 2)
		dev->reg_cache[reg] = ac97_reg_uncached_read(dev, reg);
}

bool
ac97_set_rate(ac97_dev *dev, uint8 reg, uint32 rate)
{
	uint32 value;
	uint32 old;
	
	if (dev->set_rate)
		return dev->set_rate(dev, reg, rate);

	value = (uint32)((rate * 48000ULL) / dev->clock); /* need 64 bit calculation for rates 96000 or higher */

	LOG(("ac97_set_rate: clock = %d, rate = %d, value = %d\n", dev->clock, rate, value));
	
	/* if double rate audio is currently enabled, divide value by 2 */
	if (ac97_reg_cached_read(dev, AC97_EXTENDED_STAT_CTRL) & 0x0002)
		value /= 2;
		
	if (value < dev->min_vsr || value > dev->max_vsr)
		return false;

	old = ac97_reg_cached_read(dev, reg);
	ac97_reg_cached_write(dev, reg, value);
	if (value != ac97_reg_uncached_read(dev, reg)) {
		LOG(("ac97_set_rate failed, new rate %d\n", ac97_reg_uncached_read(dev, reg)));
		ac97_reg_cached_write(dev, reg, old);
		return false;
	}
	LOG(("ac97_set_rate done\n"));
	return true;
}

bool
ac97_get_rate(ac97_dev *dev, uint8 reg, uint32 *rate)
{
	uint32 value;

	if (dev->get_rate)
		return dev->get_rate(dev, reg, rate);

	value = ac97_reg_cached_read(dev, reg);
	if (value == 0)
		return false;

	/* if double rate audio is currently enabled, multiply value by 2 */
	if (ac97_reg_cached_read(dev, AC97_EXTENDED_STAT_CTRL) & 0x0002)
		value *= 2;

	*rate = (uint32)((value * (uint64)dev->clock) / 48000); /* need 64 bit calculation to avoid overflow*/
	return true;
}

void
ac97_set_clock(ac97_dev *dev, uint32 clock)
{
	LOG(("ac97_set_clock: clock = %d\n", clock));
	dev->clock = clock;
	ac97_detect_rates(dev);
	ac97_dump_capabilities(dev);
}

void
ac97_detect_capabilities(ac97_dev *dev)
{
	uint16 val;
	
	val = ac97_reg_cached_read(dev, AC97_RESET);
	if (val & 0x0001)
		dev->capabilities |= CAP_PCM_MIC;
	if (val & 0x0004)
		dev->capabilities |= CAP_BASS_TREBLE_CTRL;
	if (val & 0x0008)
		dev->capabilities |= CAP_SIMULATED_STEREO;
	if (val & 0x0010)
		dev->capabilities |= CAP_HEADPHONE_OUT;
	if (val & 0x0020)
		dev->capabilities |= CAP_LAUDNESS;
	if (val & 0x0040)
		dev->capabilities |= CAP_DAC_18BIT;
	if (val & 0x0080)
		dev->capabilities |= CAP_DAC_20BIT;
	if (val & 0x0100)
		dev->capabilities |= CAP_ADC_18BIT;
	if (val & 0x0200)
		dev->capabilities |= CAP_ADC_20BIT;
	if (val & 0x7C00)
		dev->capabilities |= CAP_3D_ENHANCEMENT;

	val = ac97_reg_cached_read(dev, AC97_EXTENDED_ID);
	if (val & EXID_VRA)
		dev->capabilities |= CAP_VARIABLE_PCM;
	if (val & EXID_DRA)
		dev->capabilities |= CAP_DOUBLE_PCM;
	if (val & EXID_SPDIF)
		dev->capabilities |= CAP_SPDIF;
	if (val & EXID_VRM)
		dev->capabilities |= CAP_VARIABLE_MIC;
	if (val & EXID_CDAC)
		dev->capabilities |= CAP_CENTER_DAC;
	if (val & EXID_SDAC)
		dev->capabilities |= CAP_SURR_DAC;
	if (val & EXID_LDAC)
		dev->capabilities |= CAP_LFE_DAC;
	if (val & EXID_AMAP)
		dev->capabilities |= CAP_AMAP;
	if ((val & (EXID_REV0 | EXID_REV1)) == 0)
		dev->capabilities |= CAP_REV21;
	if ((val & (EXID_REV0 | EXID_REV1)) == EXID_REV0)
		dev->capabilities |= CAP_REV22;
	if ((val & (EXID_REV0 | EXID_REV1)) == EXID_REV1)
		dev->capabilities |= CAP_REV23;
		
	ac97_detect_rates(dev);
}		

void
ac97_detect_rates(ac97_dev *dev)
{
	uint32 oldrate;
	
	dev->capabilities &= ~CAP_PCM_RATE_MASK;

	if (!ac97_get_rate(dev, AC97_PCM_FRONT_DAC_RATE, &oldrate))
		oldrate = 48000;
	
	if (ac97_set_rate(dev, AC97_PCM_FRONT_DAC_RATE, 20000))
		dev->capabilities |= CAP_PCM_RATE_CONTINUOUS;
	if (ac97_set_rate(dev, AC97_PCM_FRONT_DAC_RATE, 8000))
		dev->capabilities |= CAP_PCM_RATE_8000;
	if (ac97_set_rate(dev, AC97_PCM_FRONT_DAC_RATE, 11025))
		dev->capabilities |= CAP_PCM_RATE_11025;
	if (ac97_set_rate(dev, AC97_PCM_FRONT_DAC_RATE, 12000))
		dev->capabilities |= CAP_PCM_RATE_12000;
	if (ac97_set_rate(dev, AC97_PCM_FRONT_DAC_RATE, 16000))
		dev->capabilities |= CAP_PCM_RATE_16000;
	if (ac97_set_rate(dev, AC97_PCM_FRONT_DAC_RATE, 22050))
		dev->capabilities |= CAP_PCM_RATE_22050;
	if (ac97_set_rate(dev, AC97_PCM_FRONT_DAC_RATE, 24000))
		dev->capabilities |= CAP_PCM_RATE_24000;
	if (ac97_set_rate(dev, AC97_PCM_FRONT_DAC_RATE, 32000))
		dev->capabilities |= CAP_PCM_RATE_32000;
	if (ac97_set_rate(dev, AC97_PCM_FRONT_DAC_RATE, 44100))
		dev->capabilities |= CAP_PCM_RATE_44100;
	if (ac97_set_rate(dev, AC97_PCM_FRONT_DAC_RATE, 48000))
		dev->capabilities |= CAP_PCM_RATE_48000;

	if (dev->capabilities & CAP_DOUBLE_PCM) {
		// enable double rate mode
		if (ac97_reg_update_bits(dev, AC97_EXTENDED_STAT_CTRL, 0x0002, 0x0002)) {
			if (ac97_set_rate(dev, AC97_PCM_FRONT_DAC_RATE, 88200))
				dev->capabilities |= CAP_PCM_RATE_88200;	
			if (ac97_set_rate(dev, AC97_PCM_FRONT_DAC_RATE, 96000))
				dev->capabilities |= CAP_PCM_RATE_96000;
			// disable double rate mode
			ac97_reg_update_bits(dev, AC97_EXTENDED_STAT_CTRL, 0x0002, 0x0000);
		}
	}
		
	ac97_set_rate(dev, AC97_PCM_FRONT_DAC_RATE, oldrate);
}

void
ac97_dump_capabilities(ac97_dev *dev)
{
	LOG(("AC97 capabilities:\n"));
	if (ac97_has_capability(dev, CAP_PCM_MIC))
		LOG(("CAP_PCM_MIC\n"));
	if (ac97_has_capability(dev, CAP_BASS_TREBLE_CTRL))
		LOG(("CAP_BASS_TREBLE_CTRL\n"));
	if (ac97_has_capability(dev, CAP_SIMULATED_STEREO))
		LOG(("CAP_SIMULATED_STEREO\n"));
	if (ac97_has_capability(dev, CAP_HEADPHONE_OUT))
		LOG(("CAP_HEADPHONE_OUT\n"));
	if (ac97_has_capability(dev, CAP_LAUDNESS))
		LOG(("CAP_LAUDNESS\n"));
	if (ac97_has_capability(dev, CAP_DAC_18BIT))
		LOG(("CAP_DAC_18BIT\n"));
	if (ac97_has_capability(dev, CAP_DAC_20BIT))
		LOG(("CAP_DAC_20BIT\n"));
	if (ac97_has_capability(dev, CAP_ADC_18BIT))
		LOG(("CAP_ADC_18BIT\n"));
	if (ac97_has_capability(dev, CAP_ADC_20BIT))
		LOG(("CAP_ADC_20BIT\n"));
	if (ac97_has_capability(dev, CAP_3D_ENHANCEMENT))
		LOG(("CAP_3D_ENHANCEMENT\n"));
	if (ac97_has_capability(dev, CAP_VARIABLE_PCM))
		LOG(("CAP_VARIABLE_PCM\n"));
	if (ac97_has_capability(dev, CAP_DOUBLE_PCM))
		LOG(("CAP_DOUBLE_PCM\n"));
	if (ac97_has_capability(dev, CAP_VARIABLE_MIC))
		LOG(("CAP_VARIABLE_MIC\n"));
	if (ac97_has_capability(dev, CAP_CENTER_DAC))
		LOG(("CAP_CENTER_DAC\n"));
	if (ac97_has_capability(dev, CAP_SURR_DAC))
		LOG(("CAP_SURR_DAC\n"));
	if (ac97_has_capability(dev, CAP_LFE_DAC))
		LOG(("CAP_LFE_DAC\n"));
	if (ac97_has_capability(dev, CAP_AMAP))
		LOG(("CAP_AMAP\n"));
	if (ac97_has_capability(dev, CAP_REV21))
		LOG(("CAP_REV21\n"));
	if (ac97_has_capability(dev, CAP_REV22))
		LOG(("CAP_REV22\n"));
	if (ac97_has_capability(dev, CAP_REV23))
		LOG(("CAP_REV23\n"));
	if (ac97_has_capability(dev, CAP_PCM_RATE_CONTINUOUS))
		LOG(("CAP_PCM_RATE_CONTINUOUS\n"));
	if (ac97_has_capability(dev, CAP_PCM_RATE_8000))
		LOG(("CAP_PCM_RATE_8000\n"));
	if (ac97_has_capability(dev, CAP_PCM_RATE_11025))
		LOG(("CAP_PCM_RATE_11025\n"));
	if (ac97_has_capability(dev, CAP_PCM_RATE_12000))
		LOG(("CAP_PCM_RATE_12000\n"));
	if (ac97_has_capability(dev, CAP_PCM_RATE_16000))
		LOG(("CAP_PCM_RATE_16000\n"));
	if (ac97_has_capability(dev, CAP_PCM_RATE_22050))
		LOG(("CAP_PCM_RATE_22050\n"));
	if (ac97_has_capability(dev, CAP_PCM_RATE_24000))
		LOG(("CAP_PCM_RATE_24000\n"));
	if (ac97_has_capability(dev, CAP_PCM_RATE_32000))
		LOG(("CAP_PCM_RATE_32000\n"));
	if (ac97_has_capability(dev, CAP_PCM_RATE_44100))
		LOG(("CAP_PCM_RATE_44100\n"));
	if (ac97_has_capability(dev, CAP_PCM_RATE_48000))
		LOG(("CAP_PCM_RATE_48000\n"));
	if (ac97_has_capability(dev, CAP_PCM_RATE_88200))
		LOG(("CAP_PCM_RATE_88200\n"));
	if (ac97_has_capability(dev, CAP_PCM_RATE_96000))
		LOG(("CAP_PCM_RATE_96000\n"));
}

bool
ac97_has_capability(ac97_dev *dev, uint64 cap)
{
	// return (dev->capabilities & cap); // does not work! GCC bug with 64 bit variables?
	return (dev->capabilities & cap) != 0;
}

/*************************************************
 * Codec specific initialization, etc.
 */

bool
ac97_reg_is_valid(ac97_dev *dev, uint8 reg)
{
	if (reg & 1)
		return false;
	if (reg > 0x7e)
		return false;

	switch (dev->codec_id) {
		case CODEC_ID_ALC201A:
			if (reg != 0x54) /* reading register 0x54 causes a semaphore timeout on the next register accessed */
				return true;
			return false;

		case CODEC_ID_AK4540:
		case CODEC_ID_AK4542:
			if (reg < 0x1e || reg == 0x20 || reg == 0x26 || reg > 0x7a)
				return true;
			return false;

		case CODEC_ID_AD1819:
		case CODEC_ID_AD1881:
		case CODEC_ID_AD1881A:
			if (reg < 0x3a || reg > 0x6e)
				return true;
			return false;

		case CODEC_ID_AD1885:
		case CODEC_ID_AD1886:
		case CODEC_ID_AD1886A:
		case CODEC_ID_AD1887:
			if (reg < 0x3c || reg == 0x5a || reg > 0x6e)
				return true;
			return false;

		case CODEC_ID_STAC9700:
		case CODEC_ID_STAC9704:
		case CODEC_ID_STAC9705:
		case CODEC_ID_STAC9708:
		case CODEC_ID_STAC9721:
		case CODEC_ID_STAC9744:
		case CODEC_ID_STAC9756:
			if (reg < 0x3c || reg > 0x58)
				return true;
			return false;
		
		default:
			return true;
	}
}

void ac97_amp_enable(ac97_dev *dev, bool yesno)
{
	switch (dev->codec_id) {	
		case CODEC_ID_CS4299A:
		case CODEC_ID_CS4299C:
		case CODEC_ID_CS4299D:
			LOG(("cs4299_amp_enable\n"));
			if (yesno)
				ac97_reg_cached_write(dev, 0x68, 0x8004);
			else
				ac97_reg_cached_write(dev, 0x68, 0);
			break;
		
		default:
			LOG(("ac97_amp_enable, reverse eamp = %d\n", dev->reversed_eamp_polarity));
			LOG(("powerdown register was = %#04x\n", ac97_reg_uncached_read(dev, AC97_POWERDOWN)));
			if (dev->reversed_eamp_polarity)
				yesno = !yesno;
			if (yesno)
				ac97_reg_cached_write(dev, AC97_POWERDOWN, ac97_reg_uncached_read(dev, AC97_POWERDOWN) & ~0x8000); /* switch on (low active) */
			else
				ac97_reg_cached_write(dev, AC97_POWERDOWN, ac97_reg_uncached_read(dev, AC97_POWERDOWN) | 0x8000); /* switch off */
			LOG(("powerdown register is = %#04x\n", ac97_reg_uncached_read(dev, AC97_POWERDOWN)));
		break;
	}
}

bool
ad1819_set_rate(ac97_dev *dev, uint8 reg, uint32 rate)
{
	uint32 value;
	
	value = (uint32)((rate * 48000ULL) / dev->clock); /* need 64 bit calculation for rates 96000 or higher */

	LOG(("ad1819_set_rate: clock = %d, rate = %d, value = %d\n", dev->clock, rate, value));
	
	if (value < 0x1B58 || value > 0xBB80)
		return false;

	switch (reg) {
		case AC97_PCM_FRONT_DAC_RATE:
			ac97_reg_cached_write(dev, AC97_AD_SAMPLE_RATE_0, value);
			return true;
		
		case AC97_PCM_L_R_ADC_RATE:
			ac97_reg_cached_write(dev, AC97_AD_SAMPLE_RATE_1, value);
			return true;

		default:
			return false;
	}
}

bool
ad1819_get_rate(ac97_dev *dev, uint8 reg, uint32 *rate)
{
	uint32 value;
	
	switch (reg) {
		case AC97_PCM_FRONT_DAC_RATE:
			value = ac97_reg_cached_read(dev, AC97_AD_SAMPLE_RATE_0);
			break;
		
		case AC97_PCM_L_R_ADC_RATE:
			value = ac97_reg_cached_read(dev, AC97_AD_SAMPLE_RATE_1);
			break;

		default:
			return false;
	}

	*rate = (uint32)((value * (uint64)dev->clock) / 48000); /* need 64 bit calculation to avoid overflow*/
	return true;
}


void default_init(ac97_dev *dev)
{
	LOG(("default_init\n"));
}

void ad1819_init(ac97_dev *dev)
{
	LOG(("ad1819_init\n"));

	/* Default config for system with single AD1819 codec */
	ac97_reg_cached_write(dev, AC97_AD_SERIAL_CONFIG, 0x7000);
	ac97_update_register_cache(dev);

	/* The AD1819 chip has proprietary  sample rate controls
	 * Setup sample rate 0 generator for DAC,
	 * Setup sample rate 1 generator for ADC,
	 * ARSR=1, DRSR=0, ALSR=1, DLSR=0
	 */
	ac97_reg_cached_write(dev, AC97_AD_MISC_CONTROL, 0x0101);
	/* connect special rate set/get functions */
	dev->set_rate = ad1819_set_rate;
	dev->get_rate = ad1819_get_rate;
	ac97_detect_rates(dev);
	ac97_set_rate(dev, AC97_PCM_FRONT_DAC_RATE, 48000);
	ac97_set_rate(dev, AC97_PCM_L_R_ADC_RATE, 48000);
}

void ad1881_init(ac97_dev *dev)
{
	LOG(("ad1881_init\n"));
	
	/* Default config for system with single AD1819 codec,
	 * BROKEN on systems with master & slave codecs */
	ac97_reg_cached_write(dev, AC97_AD_SERIAL_CONFIG, 0x7000);
	ac97_update_register_cache(dev);

	/* Setup DAC and ADC rate generator assignments compatible with AC97 */
	ac97_reg_cached_write(dev, AC97_AD_MISC_CONTROL, 0x0404);

	/* Setup variable frame rate limits */
	dev->min_vsr = 0x1B58;	/*  7kHz */
	dev->max_vsr = 0xBB80;	/* 48kHz */
}

void ad1885_init(ac97_dev *dev)
{
	LOG(("ad1885_init\n"));
	ad1881_init(dev);
	
	/* disable jack sense 0 and 1 (JS0, JS1) to turn off automatic mute */
	ac97_reg_cached_write(dev, AC97_AD_JACK_SENSE, ac97_reg_cached_read(dev, AC97_AD_JACK_SENSE) | 0x0300);
}

void ad1886_init(ac97_dev *dev)
{
	LOG(("ad1886_init\n"));
	ad1881_init(dev);
	
	/* disable jack sense */
	ac97_reg_cached_write(dev, AC97_AD_JACK_SENSE, ac97_reg_cached_read(dev, AC97_AD_JACK_SENSE) | 0x0100);
}

void ad1980_init(ac97_dev *dev)
{
	LOG(("ad1980_init\n"));

	/* Select only master codec,
	 * SPDIF and DAC are linked
	 */
	ac97_reg_cached_write(dev, AC97_AD_SERIAL_CONFIG, 0x1001);
	ac97_update_register_cache(dev);
	
	/* Select Line-out driven with mixer data (not surround data)
	 * Select Headphone-out driven with mixer data (not surround data),
	 * LOSEL = 0, HPSEL = 1
	 * XXX this one needs to be changed to support surround	out
	 */
	ac97_reg_cached_write(dev, AC97_AD_MISC_CONTROL, 0x0400);
}

void alc650_init(ac97_dev *dev)
{
	LOG(("alc650_init\n"));

	/* Enable Surround, LFE and Center downmix into Line-out,
	 * Set Surround-out as duplicated Line-out.
	 */
	ac97_reg_cached_write(dev, AC97_ALC650_MULTI_CHAN_CTRL, 0x0007);

	/* Set Surround DAC Volume to 0dB
	 * Set Center/LFE DAC Volume to 0dB
	 * (but both should already be set, as these are hardware reset defaults)
	 */
	ac97_reg_cached_write(dev, AC97_ALC650_SURR_VOLUME, 0x0808);
	ac97_reg_cached_write(dev, AC97_ALC650_CEN_LFE_VOLUME, 0x0808);
}

void stac9708_init(ac97_dev *dev)
{
	LOG(("stac9708_init\n"));
	/* ALSA initializes some registers that according to the
	 * documentation for this codec do not exist. If the
	 * following doesn't work, we may need to do that, too.
	 */
	/* The Analog Special reg is at 0x6C, other codecs have it at 0x6E */
	/* Set Analog Special to default (DAC/ADC -6dB disabled) */
	ac97_reg_cached_write(dev, 0x6C, 0x0000);
	/* Set Multi Channel to default */
	ac97_reg_cached_write(dev, 0x74, 0x0000);
}

void stac9721_init(ac97_dev *dev)
{
	LOG(("stac9721_init\n"));
	/* Set Analog Special to default (DAC/ADC -6dB disabled) */
	ac97_reg_cached_write(dev, 0x6E, 0x0000);
	/* Enable register 0x72 */
	ac97_reg_cached_write(dev, 0x70, 0xabba);
	/* Set Analog Current to -50% */
	ac97_reg_cached_write(dev, 0x72, 0x0002);
	/* Set Multi Channel to default */
	ac97_reg_cached_write(dev, 0x74, 0x0000);
	/* Enable register 0x78 */
	ac97_reg_cached_write(dev, 0x76, 0xabba);
	/* Set Clock Access to default */
	ac97_reg_cached_write(dev, 0x78, 0x0000);
}

void stac9744_init(ac97_dev *dev)
{
	LOG(("stac9744_init\n"));
	/* Set Analog Special to default (DAC/ADC -6dB disabled) */
	ac97_reg_cached_write(dev, 0x6E, 0x0000);
	/* Enable register 0x72 */
	ac97_reg_cached_write(dev, 0x70, 0xabba);
	/* Set Analog Current to -50% */
	ac97_reg_cached_write(dev, 0x72, 0x0002);
	/* Set Multi Channel to default */
	ac97_reg_cached_write(dev, 0x74, 0x0000);
	/* Enable register 0x78 */
	ac97_reg_cached_write(dev, 0x76, 0xabba);
	/* Set Clock Access to default */
	ac97_reg_cached_write(dev, 0x78, 0x0000);
}

void stac9756_init(ac97_dev *dev)
{
	LOG(("stac9756_init\n"));
	/* Set Analog Special to default (AC97 all-mix, DAC/ADC -6dB disabled) */
	ac97_reg_cached_write(dev, 0x6E, 0x1000);
	/* Enable register 0x72 */
	ac97_reg_cached_write(dev, 0x70, 0xabba);
	/* Set Analog Current to -50% */
	ac97_reg_cached_write(dev, 0x72, 0x0002);
	/* Set Multi Channel to default */
	ac97_reg_cached_write(dev, 0x74, 0x0000);
	/* Enable register 0x78 */
	ac97_reg_cached_write(dev, 0x76, 0xabba);
	/* Set Clock Access to default */
	ac97_reg_cached_write(dev, 0x78, 0x0000);
}

void tr28028_init(ac97_dev *dev)
{
	LOG(("tr28028_init\n"));
	ac97_reg_cached_write(dev, AC97_POWERDOWN, 0x0300);
	ac97_reg_cached_write(dev, AC97_POWERDOWN, 0x0000);
	ac97_reg_cached_write(dev, AC97_SURR_VOLUME, 0x0000);
	ac97_reg_cached_write(dev, AC97_SPDIF_CONTROL, 0x0000);
}

void wm9701_init(ac97_dev *dev)
{
	LOG(("wm9701_init\n"));
	/* ALSA writes some of these registers, but the codec
	 * documentation states explicitly that 0x38 and 0x70 to 0x74
	 * are not used in the WM9701A
	 */

	/* DVD noise patch (?) */
	ac97_reg_cached_write(dev, 0x5a, 0x0200);
}

void wm9703_init(ac97_dev *dev)
{
	LOG(("wm9703_init\n"));
	/* Set front mixer value to unmuted */
	ac97_reg_cached_write(dev, 0x72, 0x0808);
	/* Disable loopback, etc */
	ac97_reg_cached_write(dev, AC97_GENERAL_PURPOSE, 0x8000);
}

void wm9704_init(ac97_dev *dev)
{
	LOG(("wm9704_init\n"));
	/* Set read DAC value to unmuted */
	ac97_reg_cached_write(dev, 0x70, 0x0808);
	/* Set front mixer value to unmuted */
	ac97_reg_cached_write(dev, 0x72, 0x0808);
	/* Set rear mixer value to unmuted */
	ac97_reg_cached_write(dev, 0x74, 0x0808);
	/* DVD noise patch (?) */
	ac97_reg_cached_write(dev, 0x5a, 0x0200);
}
