/*
 *  Driver for AMD InterWave soundcard
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *
 *
 *   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.
 *
 *   1999/07/22		Erik Inge Bolso <knan@mo.himolde.no>
 *			* mixer group handlers
 *
 */

#define __SND_OSS_COMPAT__
#define SND_MAIN_OBJECT_FILE
#include "../include/driver.h"
#include "../include/gus.h"
#include "../include/cs4231.h"
#ifdef SND_STB
#include "../include/tea6330t.h"
#endif
#include "../include/initval.h"

int snd_index[SND_CARDS] = SND_DEFAULT_IDX;	/* Index 1-MAX */
char *snd_id[SND_CARDS] = SND_DEFAULT_STR;	/* ID for this card */
int snd_port[SND_CARDS] = SND_DEFAULT_PORT;	/* 0x210,0x220,0x230,0x240,0x250,0x260 */
#ifdef SND_STB
int snd_port_tc[SND_CARDS] = SND_DEFAULT_PORT;	/* 0x350,0x360,0x370,0x380 */
#endif
int snd_irq[SND_CARDS] = SND_DEFAULT_IRQ;	/* 2,3,5,9,11,12,15 */
int snd_dma1[SND_CARDS] = SND_DEFAULT_DMA;	/* 0,1,3,5,6,7 */
int snd_dma2[SND_CARDS] = SND_DEFAULT_DMA;	/* 0,1,3,5,6,7 */
int snd_dma1_size[SND_CARDS] = SND_DEFAULT_DMA_SIZE;	/* 8,16,32,64,128 */
int snd_dma2_size[SND_CARDS] = SND_DEFAULT_DMA_SIZE;	/* 8,16,32,64,128 */
int snd_joystick_dac[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 29};
				/* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */
int snd_midi[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 0};
int snd_pcm_voices[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 2};
int snd_effect[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 0};
MODULE_PARM(snd_index, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_index, "Index value for InterWave soundcard.");
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SND_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for InterWave soundcard.");
MODULE_PARM(snd_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_port, "Port # for InterWave driver.");
MODULE_PARM(snd_port_tc, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_port_tc, "Tone control (TEA6330T - i2c bus) port # for InterWave driver.");
MODULE_PARM(snd_irq, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_irq, "IRQ # for InterWave driver.");
MODULE_PARM(snd_dma1, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma1, "DMA1 # for InterWave driver.");
MODULE_PARM(snd_dma2, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma2, "DMA2 # for InterWave driver.");
MODULE_PARM(snd_dma1_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma1_size, "DMA1 size in kB for InterWave driver.");
MODULE_PARM(snd_dma2_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma2_size, "DMA2 size in kB for InterWave driver.");
MODULE_PARM(snd_joystick_dac, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for InterWave driver.");
MODULE_PARM(snd_midi, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_midi, "MIDI UART enable for InterWave driver.");
MODULE_PARM(snd_pcm_voices, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_pcm_voices, "Reserved PCM voices for InterWave driver.");
MODULE_PARM(snd_effect, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_effect, "Effects enable for InterWave driver.");

struct snd_interwave {
	snd_irq_t *irqptr;
	snd_dma_t *dma1ptr;
	snd_dma_t *dma2ptr;
	snd_card_t *card;
	snd_gus_card_t *gus;
	snd_pcm_t *pcm;
	snd_pcm_t *pcm1;	/* GF1 compatible PCM interface */
	snd_kmixer_t *mixer;
	snd_rawmidi_t *midi_uart;
#ifdef SND_STB
	struct snd_i2c_bus *i2c;
#endif
	unsigned short gus_status_reg;
	unsigned short pcm_status_reg;
#ifdef CONFIG_ISAPNP
	struct isapnp_dev *dev;
#ifdef SND_STB
	struct isapnp_dev *devtc;
#endif
#endif
};

static struct snd_interwave *snd_interwave_cards[SND_CARDS] = SND_DEFAULT_PTR;

#ifdef CONFIG_ISAPNP
static unsigned int snd_interwave_pnpids[] = {
#ifndef SND_STB
	/* Gravis UltraSound Plug & Play */
	(ISAPNP_VENDOR('G','R','V')<<16)|ISAPNP_DEVICE(0x0001),   /* DEVICE */
	(ISAPNP_VENDOR('G','R','V')<<16)|ISAPNP_FUNCTION(0x0000), /* Synth & Codec */
	/* STB SoundRage32 */
	(ISAPNP_VENDOR('S','T','B')<<16)|ISAPNP_DEVICE(0x011a),   /* DEVICE */
	(ISAPNP_VENDOR('S','T','B')<<16)|ISAPNP_FUNCTION(0x0010), /* Synth & Codec */
	/* MED3210 */
	(ISAPNP_VENDOR('D','X','P')<<16)|ISAPNP_DEVICE(0x3201),   /* DEVICE */
	(ISAPNP_VENDOR('D','X','P')<<16)|ISAPNP_FUNCTION(0x0010), /* Synth & Codec */
	/* InterWave STB with TEA6330T */
	(ISAPNP_VENDOR('A','D','V')<<16)|ISAPNP_DEVICE(0x550a),   /* DEVICE */
	(ISAPNP_VENDOR('A','D','V')<<16)|ISAPNP_FUNCTION(0x0010), /* Synth & Codec */
	/* Dynasonix Pro */
	/* This device also have CDC1117:DynaSonix Pro Audio Effects Processor */
	(ISAPNP_VENDOR('C','D','C')<<16)|ISAPNP_DEVICE(0x1111),   /* DEVICE */
	(ISAPNP_VENDOR('C','D','C')<<16)|ISAPNP_FUNCTION(0x1112), /* Synth & Codec */	
	/* Panasonic PCA761AW Audio Card */
	(ISAPNP_VENDOR('A','D','V')<<16)|ISAPNP_DEVICE(0x55ff),   /* DEVICE */
	(ISAPNP_VENDOR('A','D','V')<<16)|ISAPNP_FUNCTION(0x0010), /* Synth & Codec */
#else
	/* InterWave STB with TEA6330T */
	(ISAPNP_VENDOR('A','D','V')<<16)|ISAPNP_DEVICE(0x550a),   /* DEVICE */
	(ISAPNP_VENDOR('A','D','V')<<16)|ISAPNP_FUNCTION(0x0010), /* Synth & Codec */
	(ISAPNP_VENDOR('A','D','V')<<16)|ISAPNP_FUNCTION(0x0015), /* Tone control */
#endif
	/* --- */
	0       /* end */
};
#endif

static void snd_interwave_use_inc(snd_card_t * card)
{
	MOD_INC_USE_COUNT;
}

static void snd_interwave_use_dec(snd_card_t * card)
{
	MOD_DEC_USE_COUNT;
}

#ifdef SND_STB
static void snd_interwave_i2c_setlines(struct snd_i2c_bus *bus,
				       int ctrl, int data)
{
	unsigned short port;

	port = (unsigned long) bus->data;
#if 0
	printk("i2c_setlines - 0x%x <- %i,%i\n", port, ctrl, data);
#endif
	outb((data << 1) | ctrl, port);
	snd_delay(1);
}

static int snd_interwave_i2c_getdataline(struct snd_i2c_bus *bus)
{
	unsigned short port;
	unsigned char res;

	port = (unsigned long) bus->data;
	res = (inb(port) & 2) >> 1;
#if 0
	printk("i2c_getdataline - 0x%x -> %i\n", port, res);
#endif
	return res;
}

static int snd_interwave_detect_stb(struct snd_interwave *iwcard,
				    snd_gus_card_t * gus, int dev)
{
	unsigned short port;
	struct snd_i2c_bus *bus;
	unsigned long flags;
	char name[32];

	port = snd_port_tc[dev];
	if (port == SND_AUTO_PORT) {
		port = 0x350;
		if (gus->gf1.port == 0x250) {
			port = 0x360;
		}
		while (port <= 0x380) {
			if (!snd_register_ioport(gus->card, port, 1,
						 "InterWave (i2c bus)", NULL))
				break;
			port += 0x10;
		}
		if (port > 0x380)
			goto __nodev;
	} else {
		if (snd_register_ioport(gus->card, port, 1,
					"InterWave (i2c bus)", NULL) < 0)
			goto __nodev;
	}
	sprintf(name, "InterWave-%i", dev);
	bus = snd_i2c_bus_new(SND_I2C_BUSID_INTERWAVE, name);
	if (!bus)
		goto __nodev;
	bus->data = (void *) ((unsigned long) port);
	bus->lock = SPIN_LOCK_UNLOCKED;
	bus->i2c_setlines = snd_interwave_i2c_setlines;
	bus->i2c_getdataline = snd_interwave_i2c_getdataline;
	SND_LOCK_I2C_BUS(bus);
	snd_i2c_reset(bus);
#if 0
	{
		int idx, ack;
		for (idx = 0; idx < 256; idx += 2) {
			snd_i2c_start(bus);
			ack = snd_i2c_sendbyte(bus, idx, 0);
			snd_i2c_stop(bus);
			if (!ack) {
				printk("i2c: scanning bus %s: found device at addr=0x%x\n",
				       bus->name, idx);
			}
		}
	}
#endif
	SND_UNLOCK_I2C_BUS(bus);
	if (snd_tea6330t_detect(bus, 0) < 0) {
		snd_i2c_bus_free(bus);
		goto __nodev;
	}
	iwcard->i2c = bus;
	return 0;

      __nodev:
	snd_unregister_ioports(gus->card);
	return -ENODEV;
}
#endif

static int snd_interwave_detect(struct snd_interwave *iwcard,
				snd_gus_card_t * gus,
				unsigned short port, int dev)
{
	unsigned long flags;
	unsigned char rev1, rev2;

	if (snd_register_ioport(gus->card, port, 16,
				"InterWave (Adlib/SB compatibility)", NULL) < 0)
		return -EBUSY;
	if (snd_register_ioport(gus->card, port + 0x100, 12,
				"InterWave (synthesizer)", NULL) < 0) {
		snd_unregister_ioports(gus->card);
		return -EBUSY;
	}
	if (snd_register_ioport(gus->card, port + 0x10c, 4,
				"InterWave (CS4231)", NULL) < 0) {
		snd_unregister_ioports(gus->card);
		return -EBUSY;
	}
	snd_gus_set_port(gus, port);
	snd_gf1_i_write8(gus, SND_GF1_GB_RESET, 0);	/* reset GF1 */
#ifdef CONFIG_SND_DEBUG_DETECT
	{
		int d;

		if (((d = snd_gf1_i_look8(gus, SND_GF1_GB_RESET)) & 0x07) != 0) {
			snd_printk("[0x%x] check 1 failed - 0x%x\n", gus->gf1.port, d);
			goto __nodev;
		}
	}
#else
	if ((snd_gf1_i_look8(gus, SND_GF1_GB_RESET) & 0x07) != 0)
		goto __nodev;
#endif
	snd_delay(16);
	snd_gf1_i_write8(gus, SND_GF1_GB_RESET, 1);	/* release reset */
	snd_delay(16);
#ifdef CONFIG_SND_DEBUG_DETECT
	{
		int d;

		if (((d = snd_gf1_i_look8(gus, SND_GF1_GB_RESET)) & 0x07) != 1) {
			snd_printk("[0x%x] check 2 failed - 0x%x\n", gus->gf1.port, d);
			goto __nodev;
		}
	}
#else
	if ((snd_gf1_i_look8(gus, SND_GF1_GB_RESET) & 0x07) != 1)
		goto __nodev;
#endif

	spin_lock_irqsave(&gus->reg_lock, flags);
	rev1 = snd_gf1_look8(gus, SND_GF1_GB_VERSION_NUMBER);
	snd_gf1_write8(gus, SND_GF1_GB_VERSION_NUMBER, ~rev1);
	rev2 = snd_gf1_look8(gus, SND_GF1_GB_VERSION_NUMBER);
	snd_gf1_write8(gus, SND_GF1_GB_VERSION_NUMBER, rev1);
	spin_unlock_irqrestore(&gus->reg_lock, flags);
	snd_printdd("[0x%x] InterWave check - rev1=0x%x, rev2=0x%x\n", gus->gf1.port, rev1, rev2);
	if ((rev1 & 0xf0) == (rev2 & 0xf0) &&
	    (rev1 & 0x0f) != (rev2 & 0x0f)) {
		snd_printdd("[0x%x] InterWave check - passed\n", gus->gf1.port);
		gus->interwave = 1;
		strcpy(gus->card->shortname, "AMD InterWave");
		gus->revision = rev1 >> 4;
		snd_gus_init_control(gus);
#ifndef SND_STB
		return 0;	/* ok.. We have an InterWave board */
#else
		return snd_interwave_detect_stb(iwcard, gus, dev);
#endif
	}
	snd_printdd("[0x%x] InterWave check - failed\n", gus->gf1.port);
      __nodev:
	snd_unregister_ioports(gus->card);
	return -ENODEV;
}

static void snd_interwave_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	unsigned short status;
	struct snd_interwave *iwcard = (struct snd_interwave *) dev_id;
	int loop, max = 100;

	if (!iwcard || !iwcard->gus || !iwcard->pcm)
		return;

	do {
		loop = 0;
		if ((status = inb(iwcard->gus_status_reg))) {
			snd_gus_interrupt(iwcard->gus, status);
			loop++;
		}
		if ((status = inb(iwcard->pcm_status_reg)) & 0x01) {	/* IRQ bit is set? */
			snd_cs4231_interrupt(iwcard->pcm, status);
			loop++;
		}
	} while (loop && --max > 0);
}

static int snd_interwave_resources(int dev, struct snd_interwave *iwcard,
				   snd_card_t * card)
{
	static int possible_irqs[] = {5, 11, 12, 9, 7, 15, 3, -1};
	static int possible_dmas[] = {0, 1, 3, 5, 6, 7, -1};
	int err;

	if ((err = snd_register_interrupt(card, "InterWave", snd_irq[dev],
				SND_IRQ_TYPE_ISA, snd_interwave_interrupt,
				iwcard, possible_irqs, 
				&iwcard->irqptr)) < 0)
		return err;
	if ((err = snd_register_dma_channel(card,
				"GFA1/CS4231 record", snd_dma1[dev],
				SND_DMA_TYPE_ISA, snd_dma1_size[dev],
				possible_dmas, &iwcard->dma1ptr)) < 0)
		return err;
	iwcard->dma1ptr->multi = 1;
	iwcard->dma1ptr->multi_match[0] = "GF1 PCM - playback";
	if (snd_dma2[dev] >= 0 && snd_dma2[dev] <= 7) {
		if ((err = snd_register_dma_channel(card,
				"CS4231 playback", snd_dma2[dev],
				SND_DMA_TYPE_ISA, snd_dma2_size[dev],
				possible_dmas, &iwcard->dma2ptr)) < 0) {
			return err;
		}
	} else {
		iwcard->dma2ptr = NULL;
	}
	return 0;
}

static void snd_interwave_reset(snd_gus_card_t * gus)
{
	snd_gf1_write8(gus, SND_GF1_GB_RESET, 0x00);
	snd_delay(16);
	snd_gf1_write8(gus, SND_GF1_GB_RESET, 0x01);
	snd_delay(16);
}

static void snd_interwave_bank_sizes(snd_gus_card_t * gus, int *sizes)
{
	int idx;
	unsigned int local;
	unsigned char d;

	for (idx = 0; idx < 4; idx++) {
		sizes[idx] = 0;
		d = 0x55;
		for (local = idx << 22;
		     local < (idx << 22) + 0x400000;
		     local += 0x40000, d++) {
			snd_gf1_poke(gus, local, d);
			snd_gf1_poke(gus, local + 1, d + 1);
#if 0
			printk("d = 0x%x, local = 0x%x, local + 1 = 0x%x, idx << 22 = 0x%x\n",
			       d,
			       snd_gf1_peek(gus, local),
			       snd_gf1_peek(gus, local + 1),
			       snd_gf1_peek(gus, idx << 22));
#endif
			if (snd_gf1_peek(gus, local) != d ||
			    snd_gf1_peek(gus, local + 1) != d + 1 ||
			    snd_gf1_peek(gus, idx << 22) != 0x55)
				break;
			sizes[idx]++;
		}
	}
#if 0
	printk("sizes: %i %i %i %i\n", sizes[0], sizes[1], sizes[2], sizes[3]);
#endif
}

struct rom_hdr {
	/* 000 */ unsigned char iwave[8];
	/* 008 */ unsigned char rom_hdr_revision;
	/* 009 */ unsigned char series_number;
	/* 010 */ unsigned char series_name[16];
	/* 026 */ unsigned char date[10];
	/* 036 */ unsigned short vendor_revision_major;
	/* 038 */ unsigned short vendor_revision_minor;
	/* 040 */ unsigned int rom_size;
	/* 044 */ unsigned char copyright[128];
	/* 172 */ unsigned char vendor_name[64];
	/* 236 */ unsigned char rom_description[128];
	/* 364 */ unsigned char pad[147];
	/* 511 */ unsigned char csum;
};

static void snd_interwave_detect_memory(snd_gus_card_t * gus)
{
	static unsigned int lmc[13] =
	{
		0x00000001, 0x00000101, 0x01010101, 0x00000401,
		0x04040401, 0x00040101, 0x04040101, 0x00000004,
		0x00000404, 0x04040404, 0x00000010, 0x00001010,
		0x10101010
	};

	int i, bank_pos, pages;
	unsigned int lmct;
	int psizes[4];
	unsigned char csum;
	struct rom_hdr romh;

#ifndef SND_STB
	gus->card->type = SND_CARD_TYPE_AMD_INTERWAVE;
#else
	gus->card->type = SND_CARD_TYPE_AMD_INTERWAVE_STB;
#endif
	snd_interwave_reset(gus);
	snd_gf1_write8(gus, SND_GF1_GB_GLOBAL_MODE, snd_gf1_read8(gus, SND_GF1_GB_GLOBAL_MODE) | 0x01);		/* enhanced mode */
	snd_gf1_write8(gus, SND_GF1_GB_MEMORY_CONTROL, 0x01);	/* DRAM I/O cycles selected */
	snd_gf1_write16(gus, SND_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SND_GF1_GW_MEMORY_CONFIG) & 0xff10) | 0x004c);
	/* ok.. simple test of memory size */
	pages = 0;
	snd_gf1_poke(gus, 0, 0x55);
	snd_gf1_poke(gus, 1, 0xaa);
#if 1
	if (snd_gf1_peek(gus, 0) == 0x55 && snd_gf1_peek(gus, 1) == 0xaa)
#else
	if (0)			/* ok.. for testing of 0k RAM */
#endif
	{
		snd_interwave_bank_sizes(gus, psizes);
		lmct = (psizes[3] << 24) | (psizes[2] << 16) |
		    (psizes[1] << 8) | psizes[0];
#if 0
		printk("lmct = 0x%08x\n", lmct);
#endif
		for (i = 0; i < sizeof(lmc) / sizeof(unsigned int); i++)
			if (lmct == lmc[i]) {
#if 0
				printk("found !!! %i\n", i);
#endif
				snd_gf1_write16(gus, SND_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SND_GF1_GW_MEMORY_CONFIG) & 0xfff0) | i);
				snd_interwave_bank_sizes(gus, psizes);
				break;
			}
		if (i >= sizeof(lmc) / sizeof(unsigned int) && !gus->gf1.enh_mode)
			 snd_gf1_write16(gus, SND_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SND_GF1_GW_MEMORY_CONFIG) & 0xfff0) | 2);
		for (i = 0; i < 4; i++) {
			gus->gf1.mem_alloc.banks_8[i].address =
			    gus->gf1.mem_alloc.banks_16[i].address = i << 22;
			gus->gf1.mem_alloc.banks_8[i].size =
			    gus->gf1.mem_alloc.banks_16[i].size = psizes[i] << 18;
			pages += psizes[i];
		}
	}
	pages <<= 18;
	gus->gf1.memory = pages;

	snd_gf1_write8(gus, SND_GF1_GB_MEMORY_CONTROL, 0x03);	/* select ROM */
	snd_gf1_write16(gus, SND_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SND_GF1_GW_MEMORY_CONFIG) & 0xff1f) | (4 << 5));
	gus->gf1.rom_banks = 0;
	gus->gf1.rom_memory = 0;
	for (bank_pos = 0; bank_pos < 16L * 1024L * 1024L; bank_pos += 4L * 1024L * 1024L) {
		for (i = 0; i < sizeof(struct rom_hdr); i++)
			*(((unsigned char *) &romh) + i) = snd_gf1_peek(gus, i + bank_pos);
#ifdef CONFIG_SND_DEBUG_ROM
		printk("ROM at 0x%06x = %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", bank_pos,
		       romh.iwave[0], romh.iwave[1], romh.iwave[2], romh.iwave[3],
		       romh.iwave[4], romh.iwave[5], romh.iwave[6], romh.iwave[7]);
#endif
		if (strncmp(romh.iwave, "INTRWAVE", 8))
			continue;	/* first check */
		csum = 0;
		for (i = 0; i < sizeof(struct rom_hdr) - 1; i++)
			csum += *(((unsigned char *) &romh) + i);
#ifdef CONFIG_SND_DEBUG_ROM
		printk("ROM checksum = 0x%x == 0x%x (computed)\n", romh.csum, (unsigned char) (256 - csum));
#endif
		if (256 - csum != romh.csum)
			continue;	/* not valid rom */
		gus->gf1.rom_banks++;
		gus->gf1.rom_present |= 1 << (bank_pos >> 22);
#ifdef SND_LITTLE_ENDIAN
		gus->gf1.rom_memory = romh.rom_size;
#else
		gus->gf1.rom_memory = ((romh.rom_size >> 24) & 0x000000ff) |
				      ((romh.rom_size >>  8) & 0x0000ff00) |
				      ((romh.rom_size <<  8) & 0x00ff0000) |
				      ((romh.rom_size << 24) & 0xff000000);
#endif
	}
#if 0
	if (gus->gf1.rom_memory > 0) {
		if (gus->gf1.rom_banks == 1 && gus->gf1.rom_present == 8)
			gus->card->type = SND_CARD_TYPE_IW_DYNASONIC;
	}
#endif
	snd_gf1_write8(gus, SND_GF1_GB_MEMORY_CONTROL, 0x00);	/* select RAM */

	if (!gus->gf1.enh_mode)
		snd_interwave_reset(gus);
}

static void snd_interwave_init(int dev, snd_gus_card_t * gus)
{
	unsigned long flags;

	/* ok.. some InterWave specific initialization */
	spin_lock_irqsave(&gus->reg_lock, flags);
	snd_gf1_write8(gus, SND_GF1_GB_SOUND_BLASTER_CONTROL, 0x00);
	snd_gf1_write8(gus, SND_GF1_GB_COMPATIBILITY, 0x1f);
	snd_gf1_write8(gus, SND_GF1_GB_DECODE_CONTROL, 0x49);
	snd_gf1_write8(gus, SND_GF1_GB_VERSION_NUMBER, 0x11);
	snd_gf1_write8(gus, SND_GF1_GB_MPU401_CONTROL_A, 0x00);
	snd_gf1_write8(gus, SND_GF1_GB_MPU401_CONTROL_B, 0x30);
	snd_gf1_write8(gus, SND_GF1_GB_EMULATION_IRQ, 0x00);
	spin_unlock_irqrestore(&gus->reg_lock, flags);
	gus->equal_irq = 1;
	gus->codec_flag = 1;
	gus->interwave = 1;
	gus->max_flag = 1;
	gus->joystick_dac = snd_joystick_dac[dev];

}

static int snd_interwave_master_volume(void *private_data, int w_flag, int *voices)
{
	return snd_cs4231_mixer_stereo_volume(private_data,
					      w_flag, voices,
					      31, 1, 0,
					      CS4231_LINE_LEFT_OUTPUT,
					      CS4231_LINE_RIGHT_OUTPUT);
}

static int snd_interwave_master_switch(void *private_data, int w_flag, unsigned int *bitmap)
{
	return snd_cs4231_mixer_stereo_switch(private_data,
					      w_flag, bitmap,
					      7, 1,
					      CS4231_LINE_LEFT_OUTPUT,
					      CS4231_LINE_RIGHT_OUTPUT);
}

static int snd_interwave_mic_volume(void *private_data, int w_flag, int *voices)
{
	return snd_cs4231_mixer_stereo_volume(private_data,
					      w_flag, voices,
					      31, 1, 0,
					      CS4231_LEFT_MIC_INPUT,
					      CS4231_RIGHT_MIC_INPUT);
}

static int snd_interwave_mic_switch(void *private_data, int w_flag, unsigned int *bitmap)
{
	return snd_cs4231_mixer_stereo_switch(private_data,
					      w_flag, bitmap,
					      7, 1,
					      CS4231_LEFT_MIC_INPUT,
					      CS4231_RIGHT_MIC_INPUT);
}

int snd_interwave_mixer_group_master(void *private_data,
				     snd_kmixer_file_t * file,
				     int w_flag,
				     snd_mixer_group_t * ugroup)
{
	cs4231_t *codec = (cs4231_t *)private_data;

	return snd_cs4231_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_interwave_master_volume,
					    codec->me_vol_master,
					    31,
					    snd_interwave_master_switch,
					    codec->me_sw_master,
					    NULL);
}

int snd_interwave_mixer_group_mic(void *private_data,
				  snd_kmixer_file_t * file,
				  int w_flag,
				  snd_mixer_group_t * ugroup)
{
	cs4231_t *codec = (cs4231_t *)private_data;

	return snd_cs4231_mixer_group_ctrl1(codec, file, w_flag, ugroup,
					    snd_interwave_mic_volume,
					    codec->me_vol_mic,
					    31,
					    snd_interwave_mic_switch,
					    codec->me_sw_mic_out,
					    codec->me_mux_mic);
}

static int snd_interwave_mixer(snd_kmixer_t * mixer, cs4231_t * codec)
{
	snd_kmixer_group_t *group;
	struct snd_stru_mixer_lib_volume1 *volume1;
	static struct snd_mixer_element_volume1_range master_range[2] = {
		{0, 31, -3450, 1200},
		{0, 31, -3450, 1200}
	};

	/* build new master volume control */
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_OUT_MASTER, 0, SND_MIXER_OSS_VOLUME, snd_interwave_mixer_group_master, codec)) == NULL)
		goto __error;
	if ((codec->me_vol_master = snd_mixer_lib_volume1(mixer, "Master Volume", 0, 2, master_range, snd_interwave_master_volume, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_vol_master) < 0)
		goto __error;
	if ((codec->me_sw_master = snd_mixer_lib_sw1(mixer, "Master Switch", 0, 2, snd_interwave_master_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_master) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_accu, codec->me_vol_master) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_master, codec->me_sw_master) < 0)
		goto __error;
	if (snd_mixer_element_route_remove(mixer, codec->me_accu, codec->me_out_master) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_sw_master, codec->me_out_master) < 0)
		goto __error;
	/* modify MIC volume control */
	if ((group = snd_mixer_group_find(mixer, SND_MIXER_IN_MIC, 0)) == NULL)
		goto __error;
#if 0
	if ((element2 = snd_mixer_element_find(mixer, SND_MIXER_IN_MIC, 0, SND_MIXER_ETYPE_INPUT)) == NULL)
		goto __error;
	if ((oelement2 = snd_mixer_element_find(mixer, "MIC Volume", 0, SND_MIXER_ETYPE_VOLUME1)) == NULL)
		goto __error;
#endif
	volume1 = (struct snd_stru_mixer_lib_volume1 *)codec->me_vol_mic->private_data;
	volume1->ranges[0].max = volume1->ranges[1].max = 31;
	volume1->ranges[0].min_dB = volume1->ranges[1].min_dB = -3450;
	volume1->ranges[0].max_dB = volume1->ranges[1].max_dB = 1200;
	volume1->control = snd_interwave_mic_volume;
	if ((codec->me_sw_mic_out = snd_mixer_lib_sw1(mixer, "MIC Switch", 0, 2, snd_interwave_mic_switch, codec)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, codec->me_sw_mic_out) < 0)
		goto __error;
	if (snd_mixer_element_route_remove(mixer, codec->me_vol_mic, codec->me_mux) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_in_mic, codec->me_mux) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_mic, codec->me_accu) < 0)
		goto __error;
	group->control = snd_interwave_mixer_group_mic;
	group->private_data = codec;
	codec->me_mux_mic = codec->me_in_mic;
	/* reassign AUXA to SYNTHESIZER */
	if (snd_mixer_element_rename(mixer, SND_MIXER_IN_AUX, 0, SND_MIXER_ETYPE_INPUT, SND_MIXER_IN_SYNTHESIZER, 0) < 0)
		goto __error;
	if (snd_mixer_element_rename(mixer, "Aux Input Volume", 0, SND_MIXER_ETYPE_VOLUME1, "Synth Input Volume", 0) < 0)
		goto __error;
	if (snd_mixer_element_rename(mixer, "Aux Input Switch", 0, SND_MIXER_ETYPE_SWITCH1, "Synth Input Switch", 0) < 0)
		goto __error;
	if (snd_mixer_group_rename(mixer, SND_MIXER_IN_AUX, 0, SND_MIXER_IN_SYNTHESIZER, 0, SND_MIXER_OSS_SYNTH) < 0)
		goto __error;
	/* reassign AUXB to CD */
	if (snd_mixer_element_rename(mixer, SND_MIXER_IN_AUX, 1, SND_MIXER_ETYPE_INPUT, SND_MIXER_IN_CD, 0) < 0)
		goto __error;
	if (snd_mixer_element_rename(mixer, "Aux Input Volume", 1, SND_MIXER_ETYPE_VOLUME1, "CD Input Volume", 0) < 0)
		goto __error;
	if (snd_mixer_element_rename(mixer, "Aux Input Switch", 1, SND_MIXER_ETYPE_SWITCH1, "CD Input Switch", 0) < 0)
		goto __error;
	if (snd_mixer_group_rename(mixer, SND_MIXER_IN_AUX, 1, SND_MIXER_IN_CD, 0, SND_MIXER_OSS_CD) < 0)
		goto __error;
	return 0;

      __error:
      	return 1;
}

#ifdef CONFIG_ISAPNP
static int snd_interwave_isapnp(int dev, struct snd_interwave *iwcard)
{
        static int idx = 0;
	static struct isapnp_card *card = NULL;
	struct isapnp_dev *pdev;
#ifndef SND_STB
	int step = 2;
#else
	int step = 3;
#endif	
        unsigned int tmp;

      __again:
	iwcard->dev = NULL;
#ifdef SND_STB
	iwcard->devtc = NULL;
#endif
	while ((tmp = snd_interwave_pnpids[idx]) != 0) {
		card = isapnp_find_card(tmp >> 16, tmp & 0xffff, card);
		if (card)
			break;
		idx += step;
	}
	if (!card) {
		snd_printdd("isapnp failed for InterWave\n");
		return -ENODEV;
	}
	tmp = snd_interwave_pnpids[idx+1];
	iwcard->dev = pdev = isapnp_find_dev(card, tmp >> 16, tmp & 0xffff, NULL);
	if (!pdev) {
                snd_printdd("isapnp failed for InterWave (Codec & Synth) - %i\n", idx);
                goto __again;           /* maybe we have another config */
        } else if (pdev->active) {
        	goto __again;
        }
#ifdef SND_STB
	tmp = snd_interwave_pnpids[idx+2];
	iwcard->devtc = isapnp_find_dev(card, tmp >> 16, tmp & 0xffff, NULL);
	if (!iwcard->devtc) {
                snd_printdd("isapnp failed for InterWave (Tone Control) - %i\n", idx);
                goto __again;           /* maybe we have another config */
        } else if (pdev->active) {
        	goto __again;
        }
#endif
	/* Synth & Codec initialization */
	if (pdev->prepare(pdev)<0)
		return -EAGAIN;
	if (snd_port[dev] != SND_AUTO_PORT) {
		isapnp_resource_change(&pdev->resource[0], snd_port[dev], 16);
		isapnp_resource_change(&pdev->resource[1], snd_port[dev] + 0x100, 12);
		isapnp_resource_change(&pdev->resource[2], snd_port[dev] + 0x10c, 4);
	}
	if (snd_dma1[dev] != SND_AUTO_DMA)
		isapnp_resource_change(&pdev->dma_resource[0], snd_dma1[dev], 1);
	if (snd_dma2[dev] != SND_AUTO_DMA)
		isapnp_resource_change(&pdev->dma_resource[1], snd_dma2[dev], 1);
	if (snd_dma2[dev] < 0)
		isapnp_resource_change(&pdev->dma_resource[1], 4, 1);
	if (snd_irq[dev] != SND_AUTO_IRQ)
		isapnp_resource_change(&pdev->irq_resource[0], snd_irq[dev], 1);
	if (pdev->activate(pdev)<0) {
		snd_printk("InterWave isapnp configure failure (out of resources?)\n");
		return -EBUSY;
	}
	snd_printdd("isapnp IW: sb port=0x%x, gf1 port=0x%x, codec port=0x%x\n", cfg.port[0], cfg.port[1], cfg.port[2]);
	snd_printdd("isapnp IW: dma1=%i, dma2=%i, irq=%i\n", cfg.dma[0], cfg.dma[1], cfg.irq[0]);
	if (pdev->resource[0].start + 0x100 != pdev->resource[1].start ||
	    pdev->resource[0].start + 0x10c != pdev->resource[2].start) {
		snd_printk("InterWave isapnp configure failure (wrong ports)\n");
		pdev->deactivate(pdev);
		return -ENOENT;
	}
	snd_port[dev] = pdev->resource[0].start;
	snd_dma1[dev] = pdev->dma_resource[0].start;
	if (snd_dma2[dev] >= 0)
		snd_dma2[dev] = pdev->dma_resource[1].start;
	snd_irq[dev] = pdev->irq_resource[0].start;
#ifdef SND_STB
	/* Tone Control initialization */
	pdev = iwcard->devtc;
	if (pdev->prepare(pdev)<0) {
		iwcard->dev->deactivate(iwcard->dev);
		return -EAGAIN;
	}
	if (snd_port_tc[dev] != SND_AUTO_PORT)
		isapnp_resource_change(&pdev->resource[0], snd_port_tc[dev], 1);
	if (pdev->activate(pdev)<0) {
		snd_printk("InterWave Tone Control isapnp configure failure (out of resources?)\n");
		iwcard->dev->deactivate(iwcard->dev);
		return -EBUSY;
	}
	snd_port_tc[dev] = pdev->resource[0].start;
	snd_printdd("isapnp IW: tone control port=0x%x\n", snd_port_tc[dev]);
#endif
	isapnp_cfg_end();
	return 0;
}

static void snd_interwave_deactivate(struct snd_interwave *iwcard)
{
	if (iwcard->dev)
		iwcard->dev->deactivate(iwcard->dev);
#ifdef SND_STB
	if (iwcard->devtc)
		iwcard->devtc->deactivate(iwcard->devtc);
#endif
}
#endif /* CONFIG_ISAPNP */

static int snd_interwave_probe(int dev, struct snd_interwave *iwcard)
{
#ifndef CONFIG_ISAPNP
	static int possible_ports[] =
			{0x210, 0x220, 0x230, 0x240, 0x250, 0x260, -1};
	int *ports = possible_ports;
#endif
	snd_card_t *card;
	snd_gus_card_t *gus = NULL;
	snd_pcm_t *pcm = NULL;
	snd_pcm_t *pcm1 = NULL;
	snd_kmixer_t *mixer = NULL;
	snd_rawmidi_t *midi_uart = NULL;
	char *str;

	card = snd_card_new(snd_index[dev], snd_id[dev],
			    snd_interwave_use_inc, snd_interwave_use_dec);
	if (!card)
		return -ENOMEM;
	card->static_data = iwcard;
#ifdef CONFIG_ISAPNP
	if (snd_interwave_isapnp(dev, iwcard)) {
		snd_card_free(card);
		return -ENODEV;
	}
#endif
	if (snd_interwave_resources(dev, iwcard, card) < 0) {
		snd_card_free(card);
		return -EBUSY;
	}
	gus = snd_gus_new_card(card,
			       snd_port[dev],
			       iwcard->irqptr,
			       iwcard->dma1ptr,
			       iwcard->dma2ptr,
			       0, 32, snd_pcm_voices[dev], snd_effect[dev]);
	if (!gus) {
		snd_card_free(card);
		return -ENOMEM;
	}
#ifndef CONFIG_ISAPNP
	if (snd_port[dev] == SND_AUTO_PORT) {
		for (ports = possible_ports; *ports >= 0; ports++) {
			if (!snd_interwave_detect(iwcard, gus, *ports, dev))
				break;
		}
		if (*ports < 0) {
			snd_card_free(card);
			return -ENODEV;
		}
	} else {
#endif
		if (snd_interwave_detect(iwcard, gus, snd_port[dev], dev)) {
			snd_card_free(card);
			return -ENODEV;
		}
#ifndef CONFIG_ISAPNP
	}
#endif
	iwcard->gus_status_reg = gus->gf1.reg_irqstat;
	iwcard->pcm_status_reg = gus->gf1.port + 0x10c + 2;
	snd_interwave_init(dev, gus);
	snd_interwave_detect_memory(gus);
	if (snd_gus_init_dma_irq(gus, 1) < 0) {
		snd_card_free(card);
		return -EINVAL;
	}
	pcm = snd_cs4231_new_device(card,
				    gus->gf1.port + 0x10c,
				    iwcard->irqptr,
				    iwcard->dma2ptr == NULL ?
				        iwcard->dma1ptr : iwcard->dma2ptr,
				    iwcard->dma1ptr,
				    CS4231_HW_INTERWAVE,
				    2);
	if (!pcm)
		goto __nodev;
	sprintf(pcm->name + strlen(pcm->name), " rev %c", gus->revision + 'A');
	strcat(pcm->name, " (codec)");
	mixer = snd_cs4231_new_mixer(pcm, 0);
	if (!mixer)
		goto __nodev;
	if (snd_pcm_voices[dev] > 0) {
		snd_kmixer_element_t *element;
		
		element = snd_mixer_element_find(mixer, SND_MIXER_IN_AUX, 0, SND_MIXER_ETYPE_INPUT);
		if (!element)
			goto __nodev;
		pcm1 = snd_gf1_pcm_new_device(gus, mixer, element, 1);
		if (!pcm1)
			goto __nodev;
	}
	if (snd_interwave_mixer(mixer, (cs4231_t *)(pcm->private_data)) < 0)
		goto __nodev;
#ifdef SND_STB
	{
		snd_kmixer_element_t *input;
		snd_kmixer_element_t *output;

		if (snd_mixer_element_rename(mixer, "Master Volume", 0, SND_MIXER_ETYPE_VOLUME1, "Master Volume", 1) < 0)
			goto __nodev;
		if (snd_mixer_element_rename(mixer, "Master Switch", 0, SND_MIXER_ETYPE_SWITCH1, "Master Switch", 1) < 0)
			goto __nodev;
		if (snd_mixer_group_rename(mixer, "Master", 0, "Master", 1, SND_MIXER_OSS_LINE3) < 0)
			goto __nodev;
		input = snd_mixer_element_find(mixer, "Master Switch", 1, SND_MIXER_ETYPE_SWITCH1);
		output = snd_mixer_element_find(mixer, SND_MIXER_OUT_MASTER, 0, SND_MIXER_ETYPE_OUTPUT);
		if (input == NULL || output == NULL)
			goto __nodev;
		if (snd_mixer_element_route_remove(mixer, input, output) < 0)
			goto __nodev;
		if (snd_tea6330t_update_mixer(mixer, iwcard->i2c, input, output, 0, 1) < 0)
			goto __nodev;
		
	}
#endif

	gus->uart_enable = snd_midi[dev];
	midi_uart = snd_gf1_rawmidi_new_device(gus);
	if (!midi_uart)
		goto __nodev;

#ifdef CONFIG_SND_SEQUENCER
	if (snd_gus_attach_synthesizer(gus) < 0)
		goto __nodev;
#endif
	if (snd_rawmidi_register(midi_uart, 0) < 0) {
#ifdef CONFIG_SND_SEQUENCER
		snd_gus_detach_synthesizer(gus);
#endif
		goto __nodev;
	}
	if (snd_mixer_register(mixer, 0) < 0) {
#ifdef CONFIG_SND_SEQUENCER
		snd_gus_detach_synthesizer(gus);
#endif
		snd_rawmidi_unregister(midi_uart);
		midi_uart = NULL;
		goto __nodev;
	}
	if (snd_pcm_register(pcm, 0) < 0) {
#ifdef CONFIG_SND_SEQUENCER
		snd_gus_detach_synthesizer(gus);
#endif
		snd_rawmidi_unregister(midi_uart);
		midi_uart = NULL;
		snd_mixer_unregister(mixer);
		mixer = NULL;
		goto __nodev;
	}
	if (pcm1 && snd_pcm_register(pcm1, 1) < 0) {
#ifdef CONFIG_SND_SEQUENCER
		snd_gus_detach_synthesizer(gus);
#endif
		snd_rawmidi_unregister(midi_uart);
		midi_uart = NULL;
		snd_mixer_unregister(mixer);
		mixer = NULL;
		snd_pcm_unregister(pcm);
		pcm = NULL;
		goto __nodev;
	}
#if 0
	snd_cs4231_debug((cs4231_t *) pcm->private_data);
#endif
	strcpy(card->abbreviation, "IW");
	strcpy(card->shortname, "InterWave");
#ifndef SND_STB
	str = "AMD InterWave";
	if (gus->gf1.rom_banks == 1 && gus->gf1.rom_present == 8)
		str = "Dynasonic 3-D";
#else
	str = "InterWave STB";
#endif
	sprintf(card->longname, "%s at 0x%x, irq %i, dma %i",
		str,
		gus->gf1.port,
		iwcard->irqptr->irq,
		iwcard->dma1ptr->dma);
	if (iwcard->dma2ptr)
		sprintf(card->longname + strlen(card->longname), "&%i",
			iwcard->dma2ptr->dma);
	snd_gf1_start(gus);

	if (!snd_card_register(card)) {
		iwcard->card = card;
		iwcard->gus = gus;
		iwcard->pcm = pcm;
		iwcard->pcm1 = pcm1;
		iwcard->mixer = mixer;
		iwcard->midi_uart = midi_uart;
		return 0;
	}
	snd_gf1_stop(gus);
#ifdef CONFIG_SND_SEQUENCER
	snd_gus_detach_synthesizer(gus);
#endif
	snd_pcm_unregister(pcm1);
	pcm1 = NULL;
	snd_pcm_unregister(pcm);
	pcm = NULL;
	snd_mixer_unregister(mixer);
	mixer = NULL;

      __nodev:
	snd_gus_init_dma_irq(gus, 0);
	if (midi_uart)
		snd_rawmidi_free(midi_uart);
	if (mixer)
		snd_mixer_free(mixer);
	if (pcm1)
		snd_pcm_free(pcm1);
	if (pcm)
		snd_pcm_free(pcm);
	snd_card_free(card);
#ifdef SND_STB
	if (iwcard->i2c) {
		snd_i2c_bus_free(iwcard->i2c);
		iwcard->i2c = NULL;
	}
#endif
	return -ENXIO;
}

static int snd_interwave_free(int dev)
{
	struct snd_interwave *iwcard;
	snd_pcm_t *pcm;

	iwcard = snd_interwave_cards[dev];
	snd_interwave_cards[dev] = NULL;
	if (iwcard) {
		snd_card_unregister(iwcard->card);
		if (iwcard->gus) {
			snd_gf1_stop(iwcard->gus);
			snd_gus_init_dma_irq(iwcard->gus, 0);
		}
#ifdef CONFIG_SND_SEQUENCER
		snd_gus_detach_synthesizer(iwcard->gus);
#endif
		if (iwcard->midi_uart)
			snd_rawmidi_unregister(iwcard->midi_uart);
		if (iwcard->mixer)
			snd_mixer_unregister(iwcard->mixer);
		if (iwcard->pcm1) {
			snd_pcm_unregister(iwcard->pcm1);
		}
		if (iwcard->pcm) {
			pcm = iwcard->pcm;
			iwcard->pcm = NULL;	/* turn off interrupts */
			snd_pcm_unregister(pcm);
		}
#ifdef CONFIG_ISAPNP
		snd_interwave_deactivate(iwcard);
#endif
#ifdef SND_STB
		if (iwcard->i2c)
			snd_i2c_bus_free(iwcard->i2c);
#endif
		snd_card_free(iwcard->card);
		snd_kfree(iwcard);
	}
	return 0;
}

int init_module(void)
{
	int dev, cards;
	struct snd_interwave *iwcard;

	for (dev = cards = 0; dev < SND_CARDS && snd_port[dev] > 0; dev++) {
		iwcard = (struct snd_interwave *) snd_kcalloc(sizeof(struct snd_interwave), GFP_KERNEL);
		if (!iwcard)
			continue;
		if (snd_interwave_probe(dev, iwcard) < 0) {
#ifdef CONFIG_ISAPNP
			snd_interwave_deactivate(iwcard);
#endif
			snd_kfree(iwcard);
			if (snd_port[dev] == SND_AUTO_PORT)
				break;
			snd_printk("InterWave soundcard #%i not found at 0x%x or device busy\n", dev + 1, snd_port[dev]);
			continue;
		}
		snd_interwave_cards[dev] = iwcard;
		cards++;
	}
	if (!cards) {
		snd_printk("InterWave soundcard #%i not found or device busy\n", dev + 1);
		return -ENODEV;
	}
	return 0;
}

void cleanup_module(void)
{
	int dev;

	for (dev = 0; dev < SND_CARDS; dev++)
		snd_interwave_free(dev);
}
