/*
 *  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 "../include/driver.h"
#include "../include/sb.h"
#include "../include/mpu401.h"
#include "../include/initval.h"
#include "../include/sb16_csp.h"
#include "../include/opl3.h"
#include "../include/emu8000.h"
#include "../include/seq_device.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

#if defined(SND_SBAWE) && defined(CONFIG_SND_SEQUENCER) && defined(CONFIG_SND_SYNTH_EMU8000)
#define SND_SBAWE_EMU8000
#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_EMU8000
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
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_EMU8000
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

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;
#ifdef CONFIG_SND_SEQUENCER
	snd_seq_device_t *synth;
#endif
#ifdef SND_SBAWE_EMU8000
	snd_seq_device_t *awe;
#endif
#ifdef CONFIG_SND_SB16_CSP
	snd_hwdep_t *csp;
#endif
#ifdef CONFIG_ISAPNP
	struct isapnp_dev *dev;
#ifdef SND_SBAWE_EMU8000
	struct isapnp_dev *devwt;
#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 */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_DEVICE(0x0029),   /* 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 Vibra16S */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_DEVICE(0x0051),   /* DEVICE */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0001), /* 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(0x0039),   /* DEVICE */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0031), /* Audio */
#ifdef SND_SBAWE_EMU8000
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0021), /* WaveTable */
#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_EMU8000
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0021), /* WaveTable */
#endif
	/* Sound Blaster AWE 32 PnP */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_DEVICE(0x0043),   /* DEVICE */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0031), /* Audio */
#ifdef SND_SBAWE_EMU8000
	(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_EMU8000
	(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_EMU8000
	(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_EMU8000
	(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_EMU8000
	(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_EMU8000
	(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_EMU8000
	(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_EMU8000
        (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_EMU8000
        (ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0022), /* WaveTable */
#endif
	/* Sound Blaster AWE 64 PnP */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_DEVICE(0x00c7),	  /* DEVICE */
	(ISAPNP_VENDOR('C','T','L')<<16)|ISAPNP_FUNCTION(0x0045), /* Audio */
#ifdef SND_SBAWE_EMU8000
        (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_EMU8000
	(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;
	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_EMU8000
	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;
	}
	codec = (sbdsp_t *) pcm->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;
	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;
	sbdsp = (sbdsp_t *) pcm->private_data;
	spin_lock_irqsave(&sbdsp->mixer.lock, flags);
	status = snd_sb16mixer_read(&sbdsp->mixer, 0x82);
	spin_unlock_irqrestore(&sbdsp->mixer.lock, 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)
{
	static int idx = 0;
	static struct isapnp_card *card = NULL;
	struct isapnp_dev *pdev;
#ifndef SND_SBAWE_EMU8000
        int step = 2;
#else
	int step = 3;
#endif
        unsigned int tmp;

      __again:
      	acard->dev = NULL;
#ifdef SND_SBAWE_EMU8000
      	acard->devwt = NULL;
#endif
	while ((tmp = snd_sb16_pnpids[idx]) != 0) {
		card = isapnp_find_card(tmp >> 16, tmp & 0xffff, card);
		if (card)
			break;
		idx += step;
	}
	if (!card) {
		snd_printdd("isapnp failed for SB16\n");
		return -ENODEV;
	}
	tmp = snd_sb16_pnpids[idx+1];
	acard->dev = pdev = isapnp_find_dev(card, tmp >> 16, tmp & 0xffff, NULL);
	if (!pdev) {
                snd_printdd("isapnp failed for SB16 - %i\n", idx);
                goto __again;           /* maybe we have another config */
        } else {
        	if (pdev->active)
        		goto __again;
        }
#ifdef SND_SBAWE_EMU8000
	tmp = snd_sb16_pnpids[idx+2];
	acard->devwt = isapnp_find_dev(card, tmp >> 16, tmp & 0xffff, NULL);
	if (!acard->devwt) {
                snd_printdd("isapnp failed for SB16 (WaveTable) - %i\n", idx);
                goto __again;           /* maybe we have another config */
        } else {
        	if (acard->devwt->active)
        		goto __again;
        }
#endif
	/* Audio 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);
	if (snd_mpu_port[dev] != SND_AUTO_PORT)
		isapnp_resource_change(&pdev->resource[1], snd_mpu_port[dev], 2);
	if (snd_fm_port[dev] != SND_AUTO_PORT)
		isapnp_resource_change(&pdev->resource[2], snd_mpu_port[dev], 4);
	if (snd_dma8[dev] != SND_AUTO_DMA)
		isapnp_resource_change(&pdev->dma_resource[0], snd_dma8[dev], 1);
	if (snd_dma16[dev] != SND_AUTO_DMA)
		isapnp_resource_change(&pdev->dma_resource[1], snd_dma16[dev], 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("SB16 isapnp configure failure (out of resources?)\n");
		return -EBUSY;
	}
	snd_port[dev] = pdev->resource[0].start;
	snd_mpu_port[dev] = pdev->resource[1].start;
	snd_fm_port[dev] = pdev->resource[2].start;
	snd_dma8[dev] = pdev->dma_resource[0].start;
	snd_dma16[dev] = pdev->dma_resource[1].start;
	snd_irq[dev] = pdev->irq_resource[0].start;
	snd_printdd("isapnp SB16: port=0x%lx, mpu port=0x%lx, fm port=0x%lx\n",
			snd_port[dev], snd_mpu_port[dev], snd_fm_port[dev]);
	snd_printdd("isapnp SB16: dma1=%i, dma2=%i, irq=%i\n",
			snd_dma8[dev], snd_dma16[dev], snd_irq[dev]);
#ifdef SND_SBAWE_EMU8000
	/* WaveTable initialization */
	pdev = acard->devwt;
	if (pdev->prepare(pdev)<0) {
		acard->dev->deactivate(acard->dev);
		return -EAGAIN;
	}
	if (snd_awe_port[dev] != SND_AUTO_PORT)
		isapnp_resource_change(&pdev->resource[0], snd_awe_port[dev], 4);
	if (pdev->activate(pdev)<0) {
		snd_printk("SB WaveTable isapnp configure failure (out of resources?)\n");
		acard->dev->deactivate(acard->dev);		
		return -EBUSY;
	}
	snd_awe_port[dev] = pdev->resource[0].start;
	/* set other two ports which aren't listed in PnP EEPROM */
	isapnp_cfg_begin(pdev->bus->number, pdev->devfn);
	isapnp_write_word(ISAPNP_CFG_PORT + (1 << 1), snd_awe_port[dev] + (0xa40 - 0x640));
	isapnp_write_word(ISAPNP_CFG_PORT + (2 << 1), snd_awe_port[dev] + (0xe40 - 0x640));
	isapnp_cfg_end();
	snd_printdd("isapnp SB16: wavetable port=0x%lx\n", pdev->resource[0].start);
#endif
	return 0;
}

static void snd_sb16_deactivate(struct snd_sb16 *acard)
{
	if (acard->dev)
		acard->dev->deactivate(acard->dev);
#ifdef SND_SBAWE_EMU8000
	if (acard->devwt)
		acard->devwt->deactivate(acard->devwt);
#endif
}
#endif /* CONFIG_ISAPNP */

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;
	int pcm_registered = 0;
	snd_kmixer_t *mixer = NULL;
	int mixer_registered = 0;
	snd_rawmidi_t *rmidi = NULL;
	int rmidi_registered = 0;
#ifdef CONFIG_SND_SEQUENCER
	snd_seq_device_t *synth = NULL;
#endif
#ifdef SND_SBAWE_EMU8000
	snd_seq_device_t *awe = NULL;
#endif
#ifdef CONFIG_SND_SB16_CSP
	snd_hwdep_t *csp = NULL;
	int csp_registered = 0;
#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;
		}
	} 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);
#ifndef CONFIG_ISAPNP
	}
#endif
	if (!pcm) {
		snd_card_free(card);
		return -ENODEV;
	}

	codec = (sbdsp_t *) pcm->private_data;
	mixer = snd_sb16dsp_new_mixer(card, &codec->mixer, codec->hardware, 0);
	if (!mixer)
		goto __nodev;

	/* FIXME: this is not working yet.. Arg should be allocated */
	/*synth = snd_opl3_new_device(card, codec->port, codec->port + 2,
				    OPL3_HW_OPL3, -1);*/
#ifdef CONFIG_SND_SEQUENCER
	snd_seq_device_register(card, 0, NULL, SND_SEQ_DEV_OPL3, NULL, 0, &synth);
#endif
#ifdef SND_SBAWE_EMU8000
	if (snd_awe_port[dev] > 0) {
		snd_seq_device_register(card, 1, "EMU-8000", SND_SEQ_DEV_EMU8000, (void*)snd_awe_port[dev], 0, &awe);
		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 (rmidi) {
		if (snd_rawmidi_register(rmidi, 0) < 0)
			goto __nodev;
		rmidi_registered = 1;
	}
	if (snd_mixer_register(mixer, 0) < 0)
		goto __nodev;
	mixer_registered = 1;

	if (snd_pcm_register(pcm, 0))
		goto __nodev;
	pcm_registered = 1;

#ifdef CONFIG_SND_SB16_CSP
	/* CSP chip on SB16ASP/AWE32 */
	csp = snd_sb_csp_new_device(codec);
	if (csp) {
		if (snd_hwdep_register(csp, 2))
			goto __nodev;
		csp_registered = 1;
	}
#endif

	/* 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;
#ifdef CONFIG_SND_SB16_CSP
		acard->csp = csp;
#endif
		acard->rmidi = rmidi;
#ifdef CONFIG_SND_SEQUENCER
		acard->synth = synth;
#endif
#ifdef SND_SBAWE_EMU8000
		acard->awe = awe;
#endif
		return 0;
	}

      __nodev:
#ifdef CONFIG_SND_SB16_CSP
	if (csp) {
		if (csp_registered)
			snd_hwdep_unregister(csp);
		else
			snd_hwdep_free(csp);
	}
#endif
#ifdef CONFIG_SND_SEQUENCER
	if (synth)
		snd_seq_device_unregister(synth);
#endif
#ifdef SND_SBAWE_EMU8000
	if (awe)
		snd_seq_device_unregister(awe);
#endif
	if (rmidi) {
		if (rmidi_registered)
			snd_rawmidi_unregister(rmidi);
		else
			snd_rawmidi_free(rmidi);
	}
	if (mixer) {
		if (mixer_registered)
			snd_mixer_unregister(mixer);
		else
			snd_mixer_free(mixer);
	}
	if (pcm) {
		if (pcm_registered)
			snd_pcm_unregister(pcm);
		else
			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);
#ifdef CONFIG_SND_SB16_CSP
		if (acard->csp)
			snd_hwdep_unregister(acard->csp);
#endif
#ifdef CONFIG_SND_SEQUENCER
		if (acard->synth)
			snd_seq_device_unregister(acard->synth);
#endif
#ifdef SND_SBAWE_EMU8000
		if (acard->awe)
			snd_seq_device_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_kfree(acard);
	}
	return 0;
}

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

	for (dev = cards = 0; dev < SND_CARDS && snd_port[dev] > 0; dev++) {
		acard = (struct snd_sb16 *) snd_kcalloc(sizeof(struct snd_sb16), GFP_KERNEL);
		if (!acard)
			continue;
		if (snd_sb16_probe(dev, acard) < 0) {
#ifdef CONFIG_ISAPNP
			snd_sb16_deactivate(acard);
#endif
			snd_kfree(acard);
			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);
}
