/*
 *  Driver for SoundBlaster 16/AWE32/AWE64 soundcards
 *  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.
 *
 */

#define SND_MAIN_OBJECT_FILE
#include "driver.h"
#include "sb.h"
#include "mpu401.h"
#include "opl3.h"
#include "emu8000.h"
#include "initval.h"

#if 0
#define SND_TEST_SB_OLD_MIDI	/* doesn't work for me (SB AWE 64) - Perex */
#endif
#if 0
#define SND_DEBUG_IRQ
#endif

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;	/* 0x220,0x240,0x260 */
int snd_mpu_port[SND_CARDS] = {0x330, 0x300,[2 ... (SND_CARDS - 1)] = -1};
int snd_fm_port[SND_CARDS] = SND_DEFAULT_PORT;	/* optional, not used */
#ifdef SND_SBAWE
int snd_awe_port[SND_CARDS] = {0x620,[1 ... (SND_CARDS - 1)] = -1};
#endif
int snd_irq[SND_CARDS] = SND_DEFAULT_IRQ;	/* 5,7,9,10 */
int snd_dma8[SND_CARDS] = SND_DEFAULT_DMA;	/* 0,1,3 */
int snd_dma8_size[SND_CARDS] = SND_DEFAULT_DMA_SIZE;	/* 8,16,32,64,128 */
int snd_dma16[SND_CARDS] = SND_DEFAULT_DMA;	/* 5,6,7 */
int snd_dma16_size[SND_CARDS] = SND_DEFAULT_DMA_SIZE;	/* 8,16,32,64,128 */
int snd_mic_agc[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 1};
#ifdef CONFIG_ISAPNP
int snd_isapnp[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 1};
#endif
#ifdef MODULE_PARM
MODULE_PARM(snd_index, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_index, "Index value for SoundBlaster 16 soundcard.");
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SND_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for SoundBlaster 16 soundcard.");
MODULE_PARM(snd_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_port, "Port # for SB16 driver.");
MODULE_PARM(snd_mpu_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_mpu_port, "MPU-401 port # for SB16 driver.");
MODULE_PARM(snd_fm_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_fm_port, "FM port # for SB16 driver.");
#ifdef SND_SBAWE
MODULE_PARM(snd_awe_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_awe_port, "AWE port # for SB16 driver.");
#endif
MODULE_PARM(snd_irq, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_irq, "IRQ # for SB16 driver.");
MODULE_PARM(snd_dma8, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma8, "8-bit DMA # for SB16 driver.");
MODULE_PARM(snd_dma16, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma16, "16-bit DMA # for SB16 driver.");
MODULE_PARM(snd_dma8_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma8_size, "Size of 8-bit DMA # for SB16 driver.");
MODULE_PARM(snd_dma16_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma16_size, "Size of 16-bit DMA # for SB16 driver.");
MODULE_PARM(snd_mic_agc, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_mic_agc, "Mic Auto-Gain-Control switch.");
#ifdef CONFIG_ISAPNP
MODULE_PARM(snd_isapnp, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_isapnp, "ISA PnP detection for specified soundcard.");
#endif
#endif

static struct snd_sb16 {
	snd_irq_t *irqptr;
	snd_dma_t *dma8ptr;
	snd_dma_t *dma16ptr;
	snd_card_t *card;
	snd_pcm_t *pcm;
	snd_kmixer_t *mixer;
	snd_rawmidi_t *rmidi;
	snd_synth_t *synth;
#ifdef SND_SBAWE
	snd_synth_t *awe;
#endif
#ifdef CONFIG_ISAPNP
	struct isapnp_logdev *logdev;
#ifdef SND_SBAWE
	struct isapnp_logdev *logdevwt;
#endif
#endif
} *snd_sb16_cards[SND_CARDS] = SND_DEFAULT_PTR;

#ifdef CONFIG_ISAPNP
static unsigned int snd_sb16_pnpids[] = {
#ifndef SND_SBAWE
	/* Sound Blaster 16 PnP */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_DEVICE(0x0024),   /* DEVICE */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0031), /* Audio */
	/* Sound Blaster 16 PnP */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_DEVICE(0x0026),   /* DEVICE */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0031), /* Audio */
	/* Sound Blaster 16 PnP */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_DEVICE(0x0027),   /* DEVICE */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0031), /* Audio */
	/* Sound Blaster 16 PnP */
	/* Note: This card also have a CTL0051:StereoEnhance device!!! */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_DEVICE(0x002b),   /* DEVICE */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0031), /* Audio */
	/* Sound Blaster Vibra16C */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_DEVICE(0x0070),	  /* DEVICE */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0001), /* Audio */
	/* Sound Blaster Vibra16X */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_DEVICE(0x00f0),   /* DEVICE */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0043), /* Audio */
#endif
	/* Sound Blaster AWE 32 PnP */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_DEVICE(0x0042),   /* DEVICE */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0031), /* Audio */
#ifdef SND_SBAWE
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0021), /* WaveTable */
#endif
	/* Sound Blaster AWE 32 PnP */
	/* Note: This card have also CTL0051:StereoEnhance device!!! */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_DEVICE(0x0044),   /* DEVICE */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0031), /* Audio */
#ifdef SND_SBAWE
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0021), /* WaveTable */
#endif
	/* Sound Blaster AWE 32 PnP */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_DEVICE(0x0048),   /* DEVICE */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0031), /* Audio */
#ifdef SND_SBAWE
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0021), /* WaveTable */
#endif
	/* Sound Blaster AWE 32 PnP */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_DEVICE(0x009c),   /* DEVICE */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0041), /* Audio */
#ifdef SND_SBAWE
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0021), /* WaveTable */
#endif
	/* Sound Blaster AWE 64 PnP */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_DEVICE(0x009d),   /* DEVICE */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0042), /* Audio */
#ifdef SND_SBAWE
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0022), /* WaveTable */
#endif
	/* Sound Blaster AWE 64 PnP Gold */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_DEVICE(0x009e),   /* DEVICE */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0044), /* Audio */
#ifdef SND_SBAWE
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0023), /* WaveTable */
#endif
	/* Sound Blaster AWE 64 PnP */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_DEVICE(0x00c1),   /* DEVICE */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0042), /* Audio */
#ifdef SND_SBAWE
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0022), /* WaveTable */
#endif
	/* Sound Blaster AWE 64 PnP */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_DEVICE(0x00c3),	/* DEVICE */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0045), /* Audio */
#ifdef SND_SBAWE
        (ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0022), /* WaveTable */
#endif
	/* Sound Blaster AWE 64 PnP */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_DEVICE(0x00c5),	/* DEVICE */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0045), /* Audio */
#ifdef SND_SBAWE
        (ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0022), /* WaveTable */
#endif
	/* Sound Blaster AWE 64 PnP */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_DEVICE(0x00e4),   /* DEVICE */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0045), /* Audio */
#ifdef SND_SBAWE
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0022), /* WaveTable */
#endif
	/* --- */
	0       /* end */
};
#endif


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

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

static snd_pcm_t *snd_sb16_detect(int dev,
				  snd_card_t * card,
				  unsigned short port,
				  snd_irq_t * irqptr,
				  snd_dma_t * dma8ptr,
				  snd_dma_t * dma16ptr)
{
	snd_pcm_t *pcm;
	snd_pcm1_t *pcm1;
	sbdsp_t *codec;
	int mpu_flag = 0;

	if (snd_register_ioport(card, port, 16, "Sound Blaster 16", NULL) < 0)
		return NULL;
	if (snd_mpu_port[dev] == 0x300 || snd_mpu_port[dev] == 0x330) {
		if (snd_register_ioport(card, snd_mpu_port[dev], 2,
					"Sound Blaster 16 - MPU-401", NULL) < 0) {
			snd_printk("sb16: port 0x%x isn't free for MPU-401, midi port is disabled\n", snd_mpu_port[dev]);
		} else {
			mpu_flag = 1;
		}
	}
	if (snd_fm_port[dev] >= 0x300 && snd_fm_port[dev] < 0x400)
		snd_register_ioport(card, snd_fm_port[dev], 4, "Sound Blaster 16 - FM", NULL);
#ifdef SND_SBAWE
	if (snd_awe_port[dev] >= 0x620 && snd_awe_port[dev] < 0x6a0) {
		if (snd_register_ioport(card, snd_awe_port[dev],
					4, "Sound Blaster AWE", NULL) < 0 ||
		    snd_register_ioport(card, snd_awe_port[dev] + (0xa40 - 0x640),
		    			4, "Sound Blaster AWE", NULL) < 0 ||
		    snd_register_ioport(card, snd_awe_port[dev] + (0xe40 - 0x640),
		    			4, "Sound Blaster AWE", NULL) < 0) {
			snd_printk("sb16: EMU8000 ports aren't available\n");
			snd_unregister_ioports(card);
			return NULL;
		}
	} else {
		snd_awe_port[dev] = -1;
	}
#endif
	pcm = snd_sb16dsp_new_device(card, port, irqptr,
				     dma8ptr, dma16ptr, SB_HW_16);
	if (!pcm) {
		snd_unregister_ioports(card);
		return NULL;
	}
	pcm1 = (snd_pcm1_t *) pcm->private_data;
	codec = (sbdsp_t *) pcm1->private_data;
	if (codec->hardware != SB_HW_16) {
		snd_printdd("SB 16 chip was not detected at 0x%x\n", port);
	      __return1:
		snd_pcm_free(pcm);
		snd_unregister_ioports(card);
		return NULL;
	}
	if (mpu_flag)
		codec->mpu_port = snd_mpu_port[dev];
	if (snd_sb16dsp_configure(pcm) < 0) {
		goto __return1;
	}
	return pcm;
}

static void snd_sb16_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	struct snd_sb16 *acard;
	snd_pcm_t *pcm;
	snd_pcm1_t *pcm1;
	sbdsp_t *sbdsp;
	unsigned long flags;
	unsigned short status;

#ifdef SND_DEBUG_IRQ
	printk("SB16: interrupt - begin\n");
#endif
	acard = (struct snd_sb16 *) dev_id;
	if (acard == NULL)
		return;
	pcm = acard->pcm;
	if (pcm == NULL)
		return;
	pcm1 = (snd_pcm1_t *) pcm->private_data;
	if (pcm1 == NULL)
		return;
	sbdsp = (sbdsp_t *) pcm1->private_data;
	snd_spin_lock(&sbdsp->mixer, mixer, &flags);
	status = snd_sb16mixer_read(&sbdsp->mixer, 0x82);
	snd_spin_unlock(&sbdsp->mixer, mixer, &flags);
#ifdef SND_DEBUG_IRQ
	printk("SB16: status = 0x%x\n", status);
#endif
	if ((status & 0x04) && acard->rmidi)
		snd_mpu401_uart_interrupt(acard->rmidi);
	if (status & 0x03)
		snd_sb16dsp_interrupt(pcm, status);
#ifdef SND_DEBUG_IRQ
	printk("SB16: interrupt - end\n");
#endif
}

static int snd_sb16_resources(int dev, struct snd_sb16 *acard,
			      snd_card_t * card)
{
	static int possible_irqs[] = {5, 9, 10, 7, -1};
	static int possible_dmas8[] = {1, 3, 0, -1};
	static int possible_dmas16[] = {5, 6, 7, -1};
	int err, ok = 0;

	if ((err = snd_register_interrupt(card,
			"Sound Blaster 16", snd_irq[dev],
			SND_IRQ_TYPE_ISA, snd_sb16_interrupt,
			acard, possible_irqs, &acard->irqptr)) < 0)
		return err;
	if ((snd_dma8[dev] >= 0 && snd_dma8[dev] <= 3) ||
	    snd_dma8[dev] == SND_AUTO_DMA) {
		if ((err = snd_register_dma_channel(card,
				"Sound Blaster 16", snd_dma8[dev],
				SND_DMA_TYPE_ISA, snd_dma8_size[dev],
				possible_dmas8, &acard->dma8ptr)) < 0)
			return err;
		ok++;
	} else {
		acard->dma8ptr = NULL;
	}
	if ((snd_dma16[dev] >= 4 && snd_dma16[dev] <= 7) ||
	    snd_dma16[dev] == SND_AUTO_DMA) {
		if ((err = snd_register_dma_channel(card,
				"Sound Blaster 16", snd_dma16[dev],
				SND_DMA_TYPE_ISA, snd_dma16_size[dev],
				possible_dmas16, &acard->dma16ptr)) < 0)
			return err;
		ok++;
	} else {
		acard->dma16ptr = NULL;
	}
	if (!ok) {
		snd_printk("SB16 - bad DMA #\n");
		return -ENOMEM;
	}
	return 0;
}

#ifdef CONFIG_ISAPNP
static int snd_sb16_isapnp(int dev, struct snd_sb16 *acard)
{
        int idx, step;
        unsigned int tmp;
	struct isapnp_dev *pdev;
	struct isapnp_logdev *logdev;
	struct isapnp_config cfg;

#ifndef SND_SBAWE
	step = 2;
#else
	step = 3;
#endif	
	idx = 0;
      __again:
      	pdev = NULL;
	while ((tmp = snd_sb16_pnpids[idx]) != 0) {
		pdev = isapnp_find_device(tmp >> 16, tmp & 0xffff, dev);
		if (pdev)
			break;
		idx += step;
	}
	if (!pdev) {
		snd_printdd("isapnp failed for SB16\n");
		return -ENODEV;
	}
	tmp = snd_sb16_pnpids[idx+1];
	acard->logdev = logdev = isapnp_find_logdev(pdev, tmp >> 16, tmp & 0xffff, 0);
	if (!acard->logdev) {
                snd_printdd("isapnp failed for SB16 - %i\n", idx);
                idx += step;
                goto __again;           /* maybe we have another config */
        }
#ifdef SND_SBAWE
	tmp = snd_sb16_pnpids[idx+2];
	acard->logdevwt = isapnp_find_logdev(pdev, tmp >> 16, tmp & 0xffff, 0);
	if (!acard->logdevwt) {
                snd_printdd("isapnp failed for SB16 (WaveTable) - %i\n", idx);
                idx += step;
                goto __again;           /* maybe we have another config */
        }
#endif
	if (isapnp_cfg_begin(logdev->dev->csn, logdev->number))
		return -EAGAIN;
	/* Audio initialization */
	if (isapnp_config_init(&cfg, logdev)) {
		isapnp_cfg_end();
		return -EAGAIN;
	}
	if (snd_port[dev] != SND_AUTO_PORT)
		cfg.port[0] = snd_port[dev];
	if (snd_mpu_port[dev] != SND_AUTO_PORT)
		cfg.port[1] = snd_mpu_port[dev];
	if (snd_fm_port[dev] != SND_AUTO_PORT)
		cfg.port[2] = snd_fm_port[dev];
	if (snd_dma8[dev] != SND_AUTO_DMA)
		cfg.dma[0] = snd_dma8[dev];
	if (snd_dma16[dev] != SND_AUTO_DMA)
		cfg.dma[1] = snd_dma16[dev];
	if (snd_irq[dev] != SND_AUTO_IRQ)
		cfg.irq[0] = snd_irq[dev];
	if (isapnp_configure(&cfg)<0) {
		snd_printk("SB16 isapnp configure failure (out of resources?)\n");
		isapnp_cfg_end();
		return -EBUSY;
	}
	snd_printdd("isapnp SB16: port=0x%x, mpu port=0x%x, fm port=0x%x\n", cfg.port[0], cfg.port[1], cfg.port[2]);
	snd_printdd("isapnp SB16: dma1=%i, dma2=%i, irq=%i\n", cfg.dma[0], cfg.dma[1], cfg.irq[0]);
	snd_port[dev] = cfg.port[0];
	snd_mpu_port[dev] = cfg.port[1];
	snd_fm_port[dev] = cfg.port[2];
	snd_dma8[dev] = cfg.dma[0];
	snd_dma16[dev] = cfg.dma[1];
	snd_irq[dev] = cfg.irq[0];
#ifdef SND_SBAWE
	/* WaveTable initialization */
	if (isapnp_config_init(&cfg, acard->logdevwt)) {
		isapnp_cfg_end();
		return -EAGAIN;
	}
	if (snd_awe_port[dev] != SND_AUTO_PORT)
		cfg.port[0] = snd_awe_port[dev];
	cfg.port_disable[0] = snd_port[dev];
	cfg.port_disable_size[0] = 16;
	cfg.port_disable[1] = snd_mpu_port[dev];
	cfg.port_disable_size[1] = 2;
	cfg.port_disable[2] = snd_fm_port[dev];
	cfg.port_disable_size[2] = 4;
	if (isapnp_configure(&cfg)<0) {
		snd_printk("SB WaveTable isapnp configure failure (out of resources?)\n");
		isapnp_cfg_end();
		return -EBUSY;
	}
	snd_awe_port[dev] = cfg.port[0];
	/* set other two ports which aren't listed in PnP EEPROM */
	isapnp_cfg_set_word(ISAPNP_CFG_PORT + (1 << 1), cfg.port[0] + (0xa40 - 0x640));
	isapnp_cfg_set_word(ISAPNP_CFG_PORT + (2 << 1), cfg.port[0] + (0xe40 - 0x640));
	snd_printdd("isapnp SB16: wavetable port=0x%x\n", cfg.port[0]);
#endif
        isapnp_activate(acard->logdev->number);
#ifdef SND_SBAWE
	isapnp_activate(acard->logdevwt->number);
#endif
	isapnp_cfg_end();
	return 0;
}

static void snd_sb16_deactivate(struct snd_sb16 *acard)
{
	if (!acard->logdev)
		return;
	if (isapnp_cfg_begin(acard->logdev->dev->csn, acard->logdev->number)<0)
		return;
	isapnp_deactivate(acard->logdev->number);
#ifdef SND_SBAWE
	if (acard->logdevwt)
		isapnp_deactivate(acard->logdevwt->number);
#endif
	isapnp_cfg_end();
}
#endif /* CONFIG_ISAPNP */

#ifdef SND_SBAWE
static int snd_sbawe3d_get_switch(snd_kmixer_t *mixer, snd_kswitch_t *kswitch, snd_switch_t *uswitch)
{
	unsigned long flags;
	sbmixer_t *sbmix;
  
	sbmix = (sbmixer_t *)kswitch->private_data;
	uswitch->type = SND_SW_TYPE_BOOLEAN;
	snd_spin_lock(sbmix, mixer, &flags);
	uswitch->value.enable =
		snd_sb16mixer_read(sbmix, SB_DSP4_3D) & 0x01 ? 1 : 0;
	/*printk("snd-sbawe: get switch %d\n", uswitch->value.enable);*/
	snd_spin_unlock(sbmix, mixer, &flags);
	return 0;
}

static int snd_sbawe3d_set_switch(snd_kmixer_t *mixer, snd_kswitch_t *kswitch, snd_switch_t *uswitch)
{
	unsigned long flags;
	sbmixer_t *sbmix;
	int change = 0;
	unsigned char reg, val;
	
	sbmix = (sbmixer_t *)kswitch->private_data;
	if (uswitch->type != SND_SW_TYPE_BOOLEAN)
		return -EINVAL;
	val = uswitch->value.enable ? 0x01 : 0x00;
	snd_spin_lock(sbmix, mixer, &flags);
	reg = snd_sb16mixer_read(sbmix, SB_DSP4_3D);
	change = (reg & 0x01) != val ? 1 : 0;
	snd_sb16mixer_write(sbmix, SB_DSP4_3D, (reg & ~0x01) | val);
	snd_spin_unlock(sbmix, mixer, &flags);
	return change;
}

snd_kswitch_t snd_sbawe3d_switch = {
	"3D Stereo Enhancement",
	(snd_get_switch_t *)snd_sbawe3d_get_switch,
	(snd_set_switch_t *)snd_sbawe3d_set_switch,
	0,
	NULL,
	NULL
};
#endif

static int snd_sb16_probe(int dev, struct snd_sb16 *acard)
{
#ifndef CONFIG_ISAPNP
	static int possible_ports[] = {0x220, 0x240, 0x260, -1};
	int *ports = possible_ports;
#endif
	sbdsp_t *codec;
	snd_card_t *card;
	snd_pcm_t *pcm = NULL;
	snd_pcm1_t *pcm1;
	snd_kmixer_t *mixer = NULL;
	snd_rawmidi_t *rmidi = NULL;
	snd_synth_t *synth = NULL;
#ifdef SND_SBAWE
	snd_synth_t *awe = NULL;
#endif

	card = snd_card_new(snd_index[dev], snd_id[dev],
			    snd_sb16_use_inc, snd_sb16_use_dec);
	if (!card)
		return -ENOMEM;
	card->static_data = acard;
	card->type = SND_CARD_TYPE_SB_16;	/* overriden in probe function */
#ifndef CONFIG_ISAPNP
	if (snd_sb16_resources(dev, acard, card) < 0) {
		snd_card_free(card);
		return -EBUSY;
	}
#endif
	pcm = NULL;
#ifndef CONFIG_ISAPNP
	if (snd_port[dev] == SND_AUTO_PORT) {
		for (ports = possible_ports; *ports >= 0; ports++) {
			pcm = snd_sb16_detect(dev, card, *ports,
					      acard->irqptr,
					      acard->dma8ptr,
					      acard->dma16ptr);
			if (pcm)
				break;
		}
		if (!pcm) {
			snd_card_free(card);
			return -ENODEV;
		}
	} else {
#else
		if (snd_isapnp[dev] && snd_sb16_isapnp(dev, acard) < 0) {
			snd_card_free(card);
			return -EBUSY;
		}
		if (snd_sb16_resources(dev, acard, card) < 0) {
			snd_card_free(card);
			return -EBUSY;
		}
#endif
		pcm = snd_sb16_detect(dev, card, snd_port[dev],
				      acard->irqptr,
				      acard->dma8ptr,
				      acard->dma16ptr);
		if (!pcm) {
			snd_card_free(card);
			return -ENODEV;
		}
#ifndef CONFIG_ISAPNP
	}
#endif
	pcm1 = (snd_pcm1_t *) pcm->private_data;
	codec = (sbdsp_t *) pcm1->private_data;
	mixer = snd_sb16dsp_new_mixer(card, &codec->mixer, codec->hardware, 0);
	if (!mixer)
		goto __nodev;

#ifdef SND_SBAWE
	snd_mixer_switch_new(mixer, &snd_sbawe3d_switch, &codec->mixer);
#endif

	synth = snd_opl3_new_device(card, codec->port, codec->port + 2,
				    OPL3_HW_OPL3, -1);
#if defined(SND_SBAWE) && defined(CONFIG_SND_EMU8000)
	if (snd_awe_port[dev] > 0) {
		awe = snd_emu8000_new_device(card, snd_awe_port[dev]);
		if (!awe) {
			snd_printk("sbawe: fatal error - EMU-8000 synthesizer not detected at 0x%x\n", snd_awe_port[dev]);
			goto __nodev;
		}
	}
#endif
#ifndef SND_TEST_SB_OLD_MIDI
	if (codec->mpu_port) {
		rmidi = snd_mpu401_uart_new_device(card, MPU401_HW_SB,
						   codec->mpu_port,
						   acard->irqptr->irq);
		if (!rmidi)
			goto __nodev;
	}
#else
	rmidi = snd_sbdsp_midi_new_device(card, pcm);
	if (!rmidi)
		goto __nodev;
#endif

	if (synth && snd_synth_register(synth) < 0)
		goto __nodev;
#ifdef SND_SBAWE
	if (awe && snd_synth_register(awe) < 0) {
		if (synth)
			snd_synth_unregister(synth);
		synth = NULL;
		goto __nodev;
	}
#endif
	if (rmidi && snd_rawmidi_register(rmidi, 0) < 0) {
		if (synth)
			snd_synth_unregister(synth);
		synth = NULL;
#ifdef SND_SBAWE
		if (awe)
			snd_synth_unregister(awe);
		awe = NULL;
#endif
		goto __nodev;
	}
	if (snd_mixer_register(mixer, 0) < 0) {
		if (synth)
			snd_synth_unregister(synth);
		synth = NULL;
#ifdef SND_SBAWE
		if (awe)
			snd_synth_unregister(awe);
		awe = NULL;
#endif
		if (rmidi)
			snd_rawmidi_unregister(rmidi);
		rmidi = NULL;
		goto __nodev;
	}
	if (snd_pcm_register(pcm, 0)) {
		if (synth)
			snd_synth_unregister(synth);
		synth = NULL;
#ifdef SND_SBAWE
		if (awe)
			snd_synth_unregister(awe);
		awe = NULL;
#endif
		if (rmidi)
			snd_rawmidi_unregister(rmidi);
		rmidi = NULL;
		snd_mixer_unregister(mixer);
		mixer = NULL;
		goto __nodev;
	}
	/* setup Mic AGC */
	snd_sb16mixer_write(&codec->mixer, SB_DSP4_MIC_AGC,
		(snd_sb16mixer_read(&codec->mixer, SB_DSP4_MIC_AGC) & 0x01) |
		(snd_mic_agc[dev] ? 0x00 : 0x01));

	strcpy(card->abbreviation, "SB16");
	strcpy(card->shortname, "Sound Blaster 16");
	sprintf(card->longname, "%s at 0x%x, irq %i, dma ",
		codec->name,
		codec->port,
		acard->irqptr->irq);
	if (acard->dma8ptr)
		sprintf(card->longname + strlen(card->longname), "%i",
			acard->dma8ptr->dma);
	if (acard->dma16ptr)
		sprintf(card->longname + strlen(card->longname), "%s%i",
			acard->dma8ptr ? "&" : "",
			acard->dma16ptr->dma);
	if (!snd_card_register(card)) {
		acard->card = card;
		acard->pcm = pcm;
		acard->mixer = mixer;
		acard->rmidi = rmidi;
		acard->synth = synth;
#ifdef SND_SBAWE
		acard->awe = awe;
#endif
		return 0;
	}
	snd_synth_unregister(synth);
	synth = NULL;
#ifdef SND_SBAWE
	snd_synth_unregister(awe);
	awe = NULL;
#endif
	snd_rawmidi_unregister(rmidi);
	rmidi = NULL;
	snd_pcm_unregister(pcm);
	pcm = NULL;
	snd_mixer_unregister(mixer);
	mixer = NULL;

      __nodev:
	if (synth)
		snd_synth_free(synth);
#ifdef SND_SBAWE
	if (awe)
		snd_synth_free(awe);
#endif
	if (rmidi)
		snd_rawmidi_free(rmidi);
	if (mixer)
		snd_mixer_free(mixer);
	if (pcm)
		snd_pcm_free(pcm);
	snd_card_free(card);
	return -ENXIO;
}

static int snd_sb16_free(int dev)
{
	struct snd_sb16 *acard;

	acard = snd_sb16_cards[dev];
	snd_sb16_cards[dev] = NULL;
	if (acard) {
		snd_card_unregister(acard->card);
		if (acard->synth)
			snd_synth_unregister(acard->synth);
#ifdef SND_SBAWE
		if (acard->awe)
			snd_synth_unregister(acard->awe);
#endif
		if (acard->rmidi)
			snd_rawmidi_unregister(acard->rmidi);
		if (acard->mixer)
			snd_mixer_unregister(acard->mixer);
		if (acard->pcm) {
			snd_pcm_unregister(acard->pcm);
			acard->pcm = NULL;
		}
		snd_card_free(acard->card);
#ifdef CONFIG_ISAPNP
		snd_sb16_deactivate(acard);
#endif
		snd_free(acard, sizeof(struct snd_sb16));
	}
	return 0;
}

int init_module(void)
{
	int dev, cards;
	struct snd_sb16 *acard;

#ifndef LINUX_2_1
	register_symtab(NULL);
#endif
	for (dev = cards = 0; dev < SND_CARDS && snd_port[dev] > 0; dev++) {
		acard = (struct snd_sb16 *) snd_malloc(sizeof(struct snd_sb16));
		if (!acard)
			continue;
		memset(acard, 0, sizeof(struct snd_sb16));
		if (snd_sb16_probe(dev, acard) < 0) {
#ifdef CONFIG_ISAPNP
			snd_sb16_deactivate(acard);
#endif
			snd_free(acard, sizeof(struct snd_sb16));
			if (snd_port[dev] == SND_AUTO_PORT)
				break;
			snd_printk("Sound Blaster 16 soundcard #%i not found at 0x%x or device busy\n", dev + 1, snd_port[dev]);
			continue;
		}
		snd_sb16_cards[dev] = acard;
		cards++;
	}
	if (!cards) {
		snd_printk("Sound Blaster 16 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_sb16_free(dev);
}
