/*
 *  ALSA card-level driver for Turtle Beach Wavefront cards 
 *                                              (Maui,Tropez,Tropez+)
 *
 *  Copyright (c) 1997-1999 by Paul Barton-Davis <pbd@op.net>
 *
 *  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/initval.h"
#include "../include/opl3.h"
#include "../include/snd_wavefront.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_cs4232_pcm_port[SND_CARDS] = SND_DEFAULT_PORT;	/* PnP setup */
int snd_cs4232_pcm_irq[SND_CARDS] = SND_DEFAULT_IRQ;	/* 5,7,9,11,12,15 */
int snd_cs4232_mpu_port[SND_CARDS] = SND_DEFAULT_PORT;	/* PnP setup */
int snd_cs4232_mpu_irq[SND_CARDS] = SND_DEFAULT_IRQ;	/* 9,11,12,15 */
int snd_ics2115_port[SND_CARDS] = SND_DEFAULT_PORT;	/* PnP setup */
int snd_ics2115_irq[SND_CARDS] = SND_DEFAULT_IRQ;       /* 2,9,11,12,15 */
int snd_fm_port[SND_CARDS] = SND_DEFAULT_PORT;	        /* PnP setup */
int snd_control_port[SND_CARDS] = SND_DEFAULT_PORT;     /* PnP setup */
int snd_wss_port[SND_CARDS] = SND_DEFAULT_PORT;	        /* PnP setup */
int snd_dma1[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[SND_CARDS] = SND_DEFAULT_DMA;	        /* 0,1,3,5,6,7 */
int snd_dma2_size[SND_CARDS] = SND_DEFAULT_DMA_SIZE;	/* 8,16,32,64,128 */
int snd_isapnp_id = -1;         /* PnP; 0x7632 = Tropez+, 0x7532 = Tropez */
#ifdef MODULE_PARM

MODULE_AUTHOR("Paul Barton-Davis <pbd@op.net>");
MODULE_DESCRIPTION("ALSA driver for Turtle Beach WaveFront soundcards");
MODULE_PARM(snd_index, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_index, "Index value for WaveFront soundcard.");
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SND_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for WaveFront soundcard.");
MODULE_PARM(snd_cs4232_pcm_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_4232_port, "Port # for CS4232 PCM interface.");
MODULE_PARM(snd_cs4232_pcm_irq, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_cs4232_pcm_irq, "IRQ # for CS4232 PCM interface.");
MODULE_PARM(snd_dma1, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma1, "DMA1 # for CS4232 PCM interface.");
MODULE_PARM(snd_dma1_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma1_size, "Size of DMA1 # for CS4232 PCM interface.");
MODULE_PARM(snd_dma2, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma2, "DMA2 # for CS4232 PCM interface.");
MODULE_PARM(snd_dma2_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma2_size, "Size of DMA2 # for CS4232 PCM interface.");
MODULE_PARM(snd_cs4232_mpu_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_cs4232_mpu_port, "port # for CS4232 MPU-401 interface.");
MODULE_PARM(snd_cs4232_mpu_irq, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_cs4232_mpu_irq, "IRQ # for CS4232 MPU-401 interface.");
MODULE_PARM(snd_ics2115_irq, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_ics2115_irq, "IRQ # for ICS2115.");
MODULE_PARM(snd_ics2115_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_ics2115_port, "Port # for ICS2115.");
MODULE_PARM(snd_fm_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_fm_port, "FM port #.");
MODULE_PARM(snd_wss_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_wss_port, "Windows Sound System port #.");

#ifdef CONFIG_ISAPNP
MODULE_PARM(snd_isapnp, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_isapnp, "ISA PnP detection for WaveFront soundcards.");
MODULE_PARM(snd_isapnp_id, "i");
MODULE_PARM_DESC(snd_isapnp_id, "ISA PnP Device ID (0x7532 or 0x7632)");
#endif

#endif MODULE_PARM

static snd_wavefront_card_t *wavefront_cards[SND_CARDS] = SND_DEFAULT_PTR;

#ifdef CONFIG_ISAPNP

static unsigned int snd_wavefront_pnpids[] = {
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_FUNCTION(0x0000), /* WSS */
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_FUNCTION(0x0010), /* CTRL */
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_FUNCTION(0xb006), /* MPU */
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_FUNCTION(0x0004), /* SYNTH */
};

static int 
snd_wavefront_isapnp (int dev, snd_wavefront_card_t *acard, int pnp_id)
{
	static struct isapnp_card *card = NULL;
	unsigned int tmp;
	struct isapnp_dev *pdev;
    
	tmp = (ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_DEVICE(pnp_id);

	/* Find the device */

	if ((card = isapnp_find_card (tmp >> 16, tmp & 0xffff, card)) == NULL) {
		snd_printk ("isapnp failed for device\n");
		return -ENODEV;
	}

	/* Check for each logical device. */

	/* "windows sound system" is logical device 1 */

	tmp = snd_wavefront_pnpids[0];
	acard->wss = isapnp_find_dev(card, tmp >> 16, tmp & 0xffff, NULL);
	if (!acard->wss) {
		snd_printk (LOGNAME "isapnp failed for WSS device\n");
		return -ENODEV;
	}

	/* control interface is logical device 2 */

	tmp = snd_wavefront_pnpids[1];
	acard->ctrl = isapnp_find_dev(card, tmp >> 16, tmp & 0xffff, NULL);
	if (!acard->ctrl) {
		snd_printk (LOGNAME "isapnp failed for CTRL device\n");
		return -ENODEV;
	}

	/* synth is logical device 4 */

	tmp = snd_wavefront_pnpids[3];
	acard->synth = isapnp_find_dev(card, tmp >> 16, tmp & 0xffff, NULL);
	if (!acard->synth) {
		snd_printk (LOGNAME "isapnp failed for Synth device\n");
		return -ENODEV;
	}

	/* Only configure the CS4232 MIDI interface is its been
	   requested. Its logical device 3
	*/

	if (snd_cs4232_mpu_port[dev] != SND_AUTO_PORT) {
		tmp = snd_wavefront_pnpids[2];
		acard->mpu = isapnp_find_dev(card, tmp >> 16, 
					     tmp & 0xffff, NULL);
		if (!acard->mpu) {
			snd_printk ("isapnp failed for CS4232 MPU-401\n");
			return -ENODEV;
		}
	}

	/* PCM/FM initialization */

	pdev = acard->wss;
	if (pdev->prepare(pdev)<0)
		return -EAGAIN;

	if (snd_cs4232_pcm_port[dev] != SND_AUTO_PORT)
		isapnp_resource_change(&pdev->resource[0], snd_cs4232_pcm_port[dev], 4);
	if (snd_fm_port[dev] != SND_AUTO_PORT)
		isapnp_resource_change(&pdev->resource[1], snd_fm_port[dev], 4);
	if (snd_wss_port[dev] != SND_AUTO_PORT)
		isapnp_resource_change(&pdev->resource[2], snd_wss_port[dev], 4);
	if (snd_ics2115_port[dev] != SND_AUTO_PORT)
		isapnp_resource_change(&pdev->resource[3], snd_ics2115_port[dev], 8);

	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_cs4232_pcm_irq[dev] != SND_AUTO_IRQ)
		isapnp_resource_change(&pdev->irq_resource[0], snd_cs4232_pcm_irq[dev], 1);

	if (pdev->activate(pdev)<0) {
		snd_printk (LOGNAME "isapnp configuration failed\n");
		return -EBUSY;
	}

	snd_cs4232_pcm_port[dev] = pdev->resource[0].start;
	snd_fm_port[dev] = pdev->resource[1].start;
	snd_wss_port[dev] = pdev->resource[2].start;
	snd_ics2115_port[dev] = pdev->resource[4].start;

	snd_dma1[dev] = pdev->dma_resource[0].start;
	snd_dma2[dev] = pdev->dma_resource[1].start == 4 ? -1 : pdev->dma_resource[1].start;
	snd_cs4232_pcm_irq[dev] = pdev->irq_resource[0].start;

	snd_printk ("isapnp CS4232: port=0x%x, fm port=0x%x, wss port=0x%x\n",
		    snd_cs4232_pcm_port[dev], snd_fm_port[dev], snd_wss_port[dev]);
	snd_printk ("isapnp CS4232: dma1=%i, dma2=%i, irq=%i\n",
		    snd_dma1[dev], snd_dma2[dev], snd_cs4232_pcm_irq[dev]);

	/* CTRL initialization */

	if (snd_control_port[dev] != SND_AUTO_PORT) {

		pdev = acard->ctrl;

		if (pdev->prepare(pdev)<0) {
			acard->wss->deactivate(acard->wss);
			return -EAGAIN;
		}
		
		if (snd_control_port[dev] != SND_AUTO_PORT)
			isapnp_resource_change(&pdev->resource[0], snd_control_port[dev], 8);
		
		if (pdev->activate(pdev)<0) {
			snd_printk(LOGNAME "isapnp configuration failed (step 2)\n");
			acard->wss->deactivate(acard->wss);
			return -EBUSY;
		}
		
		snd_control_port[dev] = pdev->resource[0].start;
		snd_printk ("isapnp CTRL: control port=0x%x\n", 
			    snd_control_port[dev]);
	}

	/* Synth initialization */

	if (snd_ics2115_port[dev] != SND_AUTO_PORT) {

		pdev = acard->synth;

		if (pdev->prepare(pdev)<0) {
			acard->wss->deactivate(acard->wss);
			if (acard->ctrl)
				acard->ctrl->deactivate(acard->ctrl);
			return -EAGAIN;
		}
		
		isapnp_resource_change(&pdev->resource[0], snd_ics2115_port[dev], 8);
		if (snd_ics2115_irq[dev] != SND_AUTO_IRQ) 
			isapnp_resource_change(&pdev->irq_resource[0], snd_ics2115_irq[dev], 1);

		if (pdev->activate(pdev)<0) {
			snd_printk(LOGNAME "isapnp synth configuration failed\n");
			acard->wss->deactivate(acard->wss);
			if (acard->ctrl)
				acard->ctrl->deactivate(acard->ctrl);
			return -EBUSY;
		}
		
		snd_ics2115_port[dev] = pdev->resource[0].start;
		snd_ics2115_irq[dev] = pdev->irq_resource[0].start;

		snd_printk (LOGNAME "isapnp SYNTH: port=0x%x, irq=%i\n", 
			    snd_ics2115_port[dev], 
			    snd_ics2115_irq[dev]);
	}

	/* MPU initialization */

	if (snd_cs4232_mpu_port[dev] != SND_AUTO_PORT) {

		pdev = acard->mpu;

		if (pdev->prepare(pdev)<0) {
			acard->wss->deactivate(acard->wss);
			if (acard->ctrl)
				acard->ctrl->deactivate(acard->ctrl);
			if (acard->synth)
				acard->synth->deactivate(acard->synth);
			return -EAGAIN;
		}

		if (snd_cs4232_mpu_port[dev] != SND_AUTO_PORT)
			isapnp_resource_change(&pdev->resource[0], snd_cs4232_mpu_port[dev], 2);
		if (snd_cs4232_mpu_irq[dev] != SND_AUTO_IRQ)
			isapnp_resource_change(&pdev->resource[0], snd_cs4232_mpu_irq[dev], 1);

		if (pdev->activate(pdev)<0) {
			snd_printk(LOGNAME "isapnp CS4232 MPU configuration failed\n");
			snd_cs4232_mpu_port[dev] = SND_AUTO_PORT;
		} else {
			snd_cs4232_mpu_port[dev] = pdev->resource[0].start;
			snd_cs4232_mpu_irq[dev] = pdev->irq_resource[0].start;
		}

		snd_printk (LOGNAME "isapnp MPU: port=0x%x, irq=%i\n", 
			    snd_cs4232_mpu_port[dev], 
			    snd_cs4232_mpu_irq[dev]);
	}

	return 0;
}

static void 
snd_wavefront_deactivate (snd_wavefront_card_t *acard)
{
	if (acard->wss)
		acard->wss->deactivate(acard->wss);
	if (acard->ctrl)
		acard->ctrl->deactivate(acard->ctrl);
	if (acard->mpu)
		acard->mpu->deactivate(acard->mpu);
	if (acard->synth)
		acard->synth->deactivate(acard->synth);
}

#endif CONFIG_ISAPNP

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

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

static void snd_wavefront_cs4232_pcm_interrupt(int irq, 
					       void *dev_id, 
					       struct pt_regs *regs)
{
	snd_wavefront_card_t *acard;
	register unsigned char status;

	acard = (snd_wavefront_card_t *) dev_id;
	if (acard == NULL || acard->pcm == NULL)
		return;
	status = inb (acard->pcm_status_reg);
	if (status & 0x01)
		snd_cs4231_interrupt(acard->pcm, status);
}

static void snd_wavefront_cs4232_mpu_interrupt(int irq, 
					       void *dev_id, 
					       struct pt_regs *regs)
{
	snd_wavefront_card_t *acard;

	acard = (snd_wavefront_card_t *) dev_id;
	if (acard == NULL || acard->cs4232_rmidi == NULL)
		return;
	snd_mpu401_uart_interrupt(acard->cs4232_rmidi);
}

static void snd_wavefront_ics2115_interrupt(int irq, 
					    void *dev_id, 
					    struct pt_regs *regs)
{
	snd_wavefront_card_t *acard;

	acard = (snd_wavefront_card_t *) dev_id;

	if (acard == NULL) 
		return;

	if (acard->wavefront.interrupts_are_midi) {
		snd_wavefront_midi_interrupt (acard);
	} else {
		snd_wavefront_internal_interrupt (acard);
	}
}

static int snd_wavefront_resources(int dev, 
				   snd_wavefront_card_t *acard,
				   snd_card_t * card)
{
	static int possible_irqs[] = {-1};
	static int possible_dmas[] = {-1};
	int err;
	
	/* First, see if we can access the relevant ports */
	/*------------------------------------------------*/

	/* CS4232 basic control port */

	if (snd_register_ioport(card, snd_cs4232_pcm_port[dev], 4, 
				"WaveFront - CS4232 (CODEC)", NULL) < 0) {
		snd_printk("ports for WaveFront CS4232 (CODEC) "
			   "are already used, try another\n");
		return -1;
	}

	/* ICS2115 MIDI control ports */

	if (snd_register_ioport(card, snd_ics2115_port[dev], 2, 
				"WaveFront - ICS2115 (MIDI)", 
				NULL) < 0) {
		snd_unregister_ioports(card);
		snd_printk("ports for WaveFront MIDI "
			   "are already used, try another\n");
		return -1;
	}

	/* ICS2115 wavetable synth ports */
	
	if (snd_register_ioport(card, snd_ics2115_port[dev]+2, 6, 
				"WaveFront - ICS2115 (Synth)", 
				NULL) < 0) {
		snd_unregister_ioports(card);
		snd_printk("ports for WaveFront Synthesizer "
			   "are already used, try another\n");
		return -1;
	}
	
	/* CS4232 MPU-401 interface */

	if (snd_cs4232_mpu_port[dev] != SND_AUTO_PORT) {
		if (snd_register_ioport(card, snd_cs4232_mpu_port[dev], 2, 
					"WaveFront - CS4232 (MPU-401)", 
					NULL) < 0) {
			snd_unregister_ioports (card);
			snd_printk("ports for WaveFront CS4232 MPU-401 "
				   "@ 0x%x are already used, try another\n",
				   snd_cs4232_mpu_port[dev]);
			return -1;
		}
	}

	/* OPL/3 FM synthesizer */
	
	if (snd_fm_port[dev] != SND_AUTO_PORT) {
		if (snd_register_ioport(card, snd_fm_port[dev], 4, 
					"WaveFront - OPL3 FM Synthesizer", NULL) < 0) {
			snd_unregister_ioports(card);
			snd_printk("ports for WaveFront FM "
				   "are already used, try another\n");
			return -1;
		}
	}

	/* store ICS2115 port, since we need it for various I/O ops */

	acard->wavefront.base = snd_ics2115_port[dev];

	/* OK, now try to allocate/verify interrupt vectors         */
	/*--------------------------------------------------------- */


	if ((err = snd_register_interrupt(card, "CS4232",
					  snd_cs4232_pcm_irq[dev], 
					  SND_IRQ_TYPE_ISA,
					  snd_wavefront_cs4232_pcm_interrupt, 
					  acard,
					  possible_irqs, 
					  &acard->cs4232_pcm_irqptr)) < 0) {
		goto __nodev;
	}

	if (snd_cs4232_mpu_port[dev] >= 0 && 
	    snd_cs4232_mpu_port[dev] != SND_AUTO_PORT && 
	    snd_cs4232_mpu_irq[dev] >= 0 &&
	    snd_cs4232_mpu_irq[dev] != SND_AUTO_IRQ) {
		if (snd_register_interrupt(card,
					   "WaveFront - CS4232/MIDI", 
					   snd_cs4232_mpu_irq[dev],
					   SND_IRQ_TYPE_ISA,
					   snd_wavefront_cs4232_mpu_interrupt,
					   acard, 
					   possible_irqs, 
					   &acard->cs4232_mpu_irqptr) < 0) {
			snd_cs4232_mpu_port[dev] = SND_AUTO_PORT;
			snd_printk("warning: can't allocate IRQ "
				   "for CS4232/MPU-401, "
				   "midi port is disabled\n");
		}
	} else {
		snd_cs4232_mpu_port[dev] = SND_AUTO_PORT;
		acard->cs4232_mpu_irqptr = NULL;
	}

	if ((err = snd_register_dma_channel(card,
					    "WaveFront - DMA1", 
					    snd_dma1[dev],
					    SND_DMA_TYPE_ISA, 
					    snd_dma1_size[dev],
					    possible_dmas, 
					    &acard->dma1ptr)) < 0) {
		goto __nodev;
		return err;
	}

	if (snd_dma2[dev] >= 0 && snd_dma2[dev] < 8) {
		if ((err = snd_register_dma_channel(card,
						    "WaveFront - DMA2", 
						    snd_dma2[dev],
						    SND_DMA_TYPE_ISA, 
						    snd_dma2_size[dev],
						    possible_dmas, 
						    &acard->dma2ptr)) < 0) {
			goto __nodev;
		}
	} else {
		acard->dma2ptr = NULL;
	}
#if 0
	if (snd_ics2115_irq[dev] == SND_AUTO_IRQ) {
		int auto_irq;

		if ((auto_irq = 
		     snd_wavefront_detect_irq (&acard->wavefront)) < 0) {
			snd_printk (LOGNAME "warning: cannot determine IRQ\n");
			goto __nodev;
		}

		snd_ics2115_irq[dev] = auto_irq;
		
	} else {
		if (snd_wavefront_interrupt_bits (snd_ics2115_irq[dev]) < 0) {
			snd_printk (LOGNAME "error: illegal IRQ\n");
			goto __nodev;
		}

		if (snd_wavefront_check_irq (&acard->wavefront,
					     snd_ics2115_irq[dev]) < 0) {
			snd_printk (LOGNAME "no interrupt on IRQ %d\n",
				    snd_ics2115_irq[dev]);
			goto __nodev;
		}
	}
#endif 0

	acard->wavefront.irq = snd_ics2115_irq[dev];

	if ((err = snd_register_interrupt(card,
					  "WaveFront - ICS2115/MIDI", 
					  snd_ics2115_irq[dev],
					  SND_IRQ_TYPE_ISA,
					  snd_wavefront_ics2115_interrupt,
					  acard, 
					  possible_irqs, 
					  &acard->ics2115_irqptr)) < 0) {
		snd_printk(LOGNAME "warning: can't allocate ICS2115/MIDI IRQ\n");
		return err;
	}

	return 0;

  __nodev:
	snd_unregister_ioports (card);
	snd_unregister_interrupts (card);
	return -1;
}

snd_hwdep_t *
snd_wavefront_new_synth (snd_card_t *card,
			 snd_wavefront_card_t *acard,
			 unsigned short port)

{
	snd_hwdep_t *wavefront_synth;

	if (snd_wavefront_detect (acard) < 0) {
		return NULL;
	}

	if (snd_wavefront_start (&acard->wavefront) < 0) {
		return NULL;
	}

	wavefront_synth = snd_hwdep_new_device (card, "WaveFront");
	strcpy (wavefront_synth->name, 
		"WaveFront (ICS2115) wavetable synthesizer");
	wavefront_synth->ops.open = snd_wavefront_synth_open;
	wavefront_synth->ops.release = snd_wavefront_synth_release;
	wavefront_synth->ops.ioctl = snd_wavefront_synth_ioctl;


	return wavefront_synth;
}

snd_hwdep_t *
snd_wavefront_new_fx (snd_card_t *card,
		      snd_wavefront_card_t *acard,
		      unsigned short port)

{
	snd_hwdep_t *fx_processor;

	if (snd_wavefront_fx_start (&acard->wavefront)) {
		snd_printk (LOGNAME "cannot initialize YSS225 FX processor");
		return NULL;
	}

	fx_processor = snd_hwdep_new_device (card, "YSS225");
	sprintf (fx_processor->name, "YSS225 FX Processor at %d", port);
	fx_processor->ops.open = snd_wavefront_fx_open;
	fx_processor->ops.release = snd_wavefront_fx_release;
	fx_processor->ops.ioctl = snd_wavefront_fx_ioctl;
	
	return fx_processor;
}

static snd_wavefront_mpu_id internal_id = internal_mpu;
static snd_wavefront_mpu_id external_id = external_mpu;

snd_rawmidi_t *
snd_wavefront_new_midi (snd_card_t *card,
			snd_wavefront_card_t *acard,
			unsigned short port,
			snd_wavefront_mpu_id mpu)

{
	snd_rawmidi_t *rmidi;
	static int first = 1;

	if (first) {
		first = 0;
		acard->wavefront.midi.base = port;
		if (snd_wavefront_midi_start (acard)) {
			snd_printk (LOGNAME 
				    "cannot initialize MIDI interface\n");
			return NULL;
		}
	}

	if ((rmidi = snd_rawmidi_new_device (card, "WaveFront MIDI")) == NULL){
		return NULL;
	}

	if (mpu == internal_mpu) {
		strcpy(rmidi->name, "WaveFront MIDI (Internal)");
		rmidi->private_data = &internal_id;
	} else {
		strcpy(rmidi->name, "WaveFront MIDI (External)");
		rmidi->private_data = &external_id;
	}

	memcpy (&rmidi->output.hw, &snd_wavefront_midi_output, 
		sizeof (snd_wavefront_midi_output));
	memcpy (&rmidi->input.hw, &snd_wavefront_midi_input, 
	        sizeof (snd_wavefront_midi_input));

	rmidi->info_flags |= SND_RAWMIDI_INFO_OUTPUT |
	                     SND_RAWMIDI_INFO_INPUT |
	                     SND_RAWMIDI_INFO_DUPLEX;

	return rmidi;
}

/* some bits so we can clean up if things fail midway */

#define PCM_REGISTERED             0x1
#define WAVEFRONT_REGISTERED       0x2
#define MIXER_REGISTERED           0x4
#define CS4232_MIDI_REGISTERED     0x8
#define ICS2115_INTMIDI_REGISTERED 0x10
#define ICS2115_EXTMIDI_REGISTERED 0x20
#define OPL3_REGISTERED            0x40
#define FX_REGISTERED              0x80

static int 
snd_wavefront_probe (int dev, snd_wavefront_card_t *acard)
{
	snd_card_t *card;
	snd_pcm_t *pcm = NULL;
	snd_kmixer_t *mixer = NULL;
	snd_rawmidi_t *cs4232_rmidi = NULL;
	snd_rawmidi_t *ics2115_internal_rmidi = NULL;
	snd_rawmidi_t *ics2115_external_rmidi = NULL;
	snd_hwdep_t *fm_synth = NULL;
	snd_hwdep_t *wavefront_synth = NULL;
	snd_hwdep_t *fx_processor = NULL;
	cs4231_t *codec;
	int err;
	int registered_bits = 0;

	if (snd_cs4232_mpu_port[dev] < 0) {
		snd_cs4232_mpu_port[dev] = SND_AUTO_PORT;
	}
	if (snd_fm_port[dev] < 0) {
		snd_fm_port[dev] = SND_AUTO_PORT;
	}

	if (snd_ics2115_port[dev] < 0) {
		snd_ics2115_port[dev] = SND_AUTO_PORT;
	}

#ifndef CONFIG_ISAPNP
	if (snd_cs4232_pcm_port[dev] == SND_AUTO_PORT ||
	    snd_ics2115_port[dev] == SND_AUTO_PORT) {
		snd_printk (LOGNAME "probing isn't supported\n");
		return -ENODEV;
	}
#endif  CONFIG_ISAPNP
	card = snd_card_new (snd_index[dev], 
			     snd_id[dev],
			     snd_wavefront_use_inc, 
			     snd_wavefront_use_dec);

	if (!card) {
		return -ENOMEM;
	}

#ifdef CONFIG_ISAPNP
	if (snd_isapnp_id == -1) {
		snd_printk (LOGNAME "option snd_isapnp_id not set\n");
		return -EBUSY;
	}

	if (snd_wavefront_isapnp (dev, acard, snd_isapnp_id) < 0) {
		if (snd_cs4232_pcm_port[dev] == SND_AUTO_PORT ||
		    snd_control_port[dev] == SND_AUTO_PORT) {
			snd_printk (LOGNAME "isapnp detection failed\n");
			snd_card_free (card);
			return -ENODEV;
		}
	}
#endif CONFIG_ISAPNP

	card->private_data = acard;
	card->private_free = NULL;

	if (snd_wavefront_resources (dev, acard, card) < 0) {
#ifdef CONFIG_ISAPNP
		snd_wavefront_deactivate (acard);
#endif CONFIG_ISAPNP
		snd_card_free (card);
		return -EBUSY;
	}

	/* --------- PCM --------------- */

	pcm = snd_cs4231_new_device (card, 
				     snd_cs4232_pcm_port[dev], 
				     acard->cs4232_pcm_irqptr,
				     acard->dma1ptr, 
				     acard->dma2ptr ? 
				     acard->dma2ptr : acard->dma1ptr,
				     CS4231_HW_DETECT, 0);
	
	if (!pcm) {
		snd_printk (LOGNAME "can't allocate PCM device\n");
		goto __nodev;
	}

	codec = (cs4231_t *) pcm->private_data;
	acard->codec = codec;
	acard->pcm_status_reg = codec->port + 2;

	if (snd_pcm_register (pcm, 0)) {
		snd_printk (LOGNAME "can't register PCM device\n");
		goto __nodev;
	}
	
	registered_bits |= PCM_REGISTERED;

	/* ------- ICS2115 Wavetable synth (w/MIDI) ------- */

	wavefront_synth = snd_wavefront_new_synth
		(card, acard, snd_ics2115_port[dev]);

	if (!wavefront_synth) {
		snd_printk (LOGNAME "can't allocate WaveFront synth device\n");
		goto __nodev;
	}

	strcpy (wavefront_synth->name, "ICS2115 Wavetable MIDI Synthesizer");
	wavefront_synth->type = SND_HWDEP_TYPE_ICS2115;

	if ((err = snd_hwdep_register (wavefront_synth, 0)) < 0) {
		snd_printk (LOGNAME "can't register WaveFront synth "
			    "device (%d)\n",
			    -err);
		goto __nodev;
	}

	registered_bits |= WAVEFRONT_REGISTERED;

	/* --------- Mixer ------------ */

	mixer = snd_cs4231_new_mixer(pcm, 0);

	if (!mixer) {
		snd_printk (LOGNAME "can't allocate mixer device\n");
		goto __nodev;
	}

	if (snd_mixer_register(mixer, 0) < 0) {
		snd_printk (LOGNAME "can't register mixer device\n");
		goto __nodev;
	}

	registered_bits |= MIXER_REGISTERED;

	/* ---------- OPL3 synth --------- */

	if (snd_fm_port[dev] != SND_AUTO_PORT) {
	        fm_synth = snd_opl3_new_device(card,
					       snd_fm_port[dev],
					       snd_fm_port[dev] + 2,
					       OPL3_HW_OPL3_CS, -1);
		if (!fm_synth) {
			snd_printk (LOGNAME "can't allocate OPL3 synth\n");
			goto __nodev;
		}

		strcpy (fm_synth->name, "Yamaha OPL3 4-Operator FM Synthesizer");
		fm_synth->type = SND_HWDEP_TYPE_OPL3;

		if (snd_hwdep_register(fm_synth, 1) < 0) {
			snd_printk (LOGNAME "can't register OPL3 synth\n");
			goto __nodev;
		}

		registered_bits |= OPL3_REGISTERED;
	}


	/* -------- CS4232 MPU-401 interface -------- */

	if (acard->cs4232_mpu_irqptr) {
		cs4232_rmidi = 
			snd_mpu401_uart_new_device(card, MPU401_HW_CS4232,
						   snd_cs4232_mpu_port[dev],
						   acard->cs4232_mpu_irqptr->irq);
		if (!cs4232_rmidi) {
			snd_printk (LOGNAME "can't allocate CS4232 MPU-401 device\n");
			goto __nodev;
		}

		if (snd_rawmidi_register(cs4232_rmidi, 0) < 0) {
			snd_printk (LOGNAME "can't register CS4232 MPU-401 device\n");
			goto __nodev;
		}

		registered_bits |= CS4232_MIDI_REGISTERED;
	}

	/* ------ ICS2115 internal MIDI ------------ */

	if (acard->ics2115_irqptr) {
		ics2115_internal_rmidi = 
			snd_wavefront_new_midi (card, 
						acard,
						snd_ics2115_port[dev],
						internal_mpu);
		if (!ics2115_internal_rmidi) {
			snd_printk (LOGNAME "can't allocate ICS2115 internal MIDI device\n");
			goto __nodev;
		}

		if (snd_rawmidi_register(ics2115_internal_rmidi, 0) < 0) {
			snd_printk (LOGNAME "can't register ICS2115 internal MIDI  device\n");
			goto __nodev;
		}
		registered_bits |= ICS2115_INTMIDI_REGISTERED;
	}

	/* ------ ICS2115 external MIDI ------------ */

	if (acard->ics2115_irqptr) {
		ics2115_external_rmidi = 
			snd_wavefront_new_midi (card, 
						acard,
						snd_ics2115_port[dev],
						external_mpu);
		if (!ics2115_external_rmidi) {
			snd_printk (LOGNAME "can't allocate ICS2115 external MIDI  device\n");
			goto __nodev;
		}

		if (snd_rawmidi_register(ics2115_external_rmidi, 1) < 0) {
			snd_printk (LOGNAME "can't register ICS2115 external MIDI device\n");
			goto __nodev;
		}

		registered_bits |= ICS2115_EXTMIDI_REGISTERED;
	}

	/* FX processor for Tropez+ */

	if (acard->wavefront.has_fx) {
		fx_processor = snd_wavefront_new_fx (card,
						     acard,
						     snd_ics2115_port[dev]);
		if (!fx_processor) {
			snd_printk (LOGNAME "can't allocate FX device\n");
			goto __nodev;
		}

		if (snd_hwdep_register (fx_processor, 2) < 0) {
			snd_printk (LOGNAME "can't register FX device\n");
			goto __nodev;
		}

		registered_bits |= FX_REGISTERED;

		strcpy (fx_processor->name, "YSS225 FX Processor");
		fx_processor->type = SND_HWDEP_TYPE_YSS225;

		strcpy(card->abbreviation, "Tropez+");
		card->type = SND_CARD_TYPE_TROPEZPLUS;
		strcpy(card->shortname, "Turtle Beach Tropez+");
	} else {
		/* Need a way to distinguish between Maui and Tropez */
		strcpy(card->abbreviation, "WaveFront");
		card->type = SND_CARD_TYPE_WAVEFRONT;
		strcpy(card->shortname, "Turtle Beach WaveFront");
	}

	/* ----- Register the card --------- */

	/* Not safe to include "Turtle Beach" in longname, due to 
	   length restrictions
	*/

	sprintf(card->longname, "%s PCM 0x%x irq %i dma%i",
		card->abbreviation,
		codec->port,
		acard->cs4232_pcm_irqptr->irq,
		acard->dma1ptr->dma);

	if (acard->dma2ptr)
		sprintf(card->longname + strlen(card->longname), "&%i",
			acard->dma2ptr->dma);

	if (snd_cs4232_mpu_port[dev] != SND_AUTO_PORT) {
		sprintf (card->longname + strlen (card->longname), 
			 " MPU-401 0x%x irq %i",
			 snd_cs4232_mpu_port[dev],
			 acard->cs4232_mpu_irqptr->irq);
	}

	sprintf (card->longname + strlen (card->longname), 
		 " SYNTH 0x%x irq %i",
		 snd_ics2115_port[dev],
		 acard->ics2115_irqptr->irq);

	if (!snd_card_register(card)) {
		acard->card = card;
		acard->pcm = pcm;
		acard->mixer = mixer;
		acard->cs4232_rmidi = cs4232_rmidi;
		acard->ics2115_internal_rmidi = ics2115_internal_rmidi;
		acard->ics2115_external_rmidi = ics2115_external_rmidi;
		acard->fm_synth = fm_synth;
		acard->wavefront_synth = wavefront_synth;
		acard->fx_processor = fx_processor;
		return 0;
	}

	snd_printk (LOGNAME "can't register card\n");

	snd_rawmidi_unregister (cs4232_rmidi);
	snd_rawmidi_unregister (ics2115_internal_rmidi);
	snd_rawmidi_unregister (ics2115_external_rmidi);
	snd_hwdep_unregister(fx_processor);
	snd_hwdep_unregister(wavefront_synth);
	snd_hwdep_unregister(fm_synth);
	snd_mixer_unregister(mixer);
	snd_pcm_unregister(pcm);
	snd_card_free(card);
	return -ENOMEM;

      __nodev:
	if (registered_bits & CS4232_MIDI_REGISTERED) {
		snd_rawmidi_unregister (cs4232_rmidi);
		cs4232_rmidi = NULL;
	}
	if (registered_bits & ICS2115_INTMIDI_REGISTERED) {
		snd_rawmidi_unregister (ics2115_internal_rmidi);
		ics2115_internal_rmidi = NULL;
	}
	if (registered_bits & ICS2115_EXTMIDI_REGISTERED) {
		snd_rawmidi_unregister (ics2115_external_rmidi);
		ics2115_external_rmidi = NULL;
	}
	if (registered_bits & FX_REGISTERED) {
		snd_hwdep_unregister(fx_processor);
		fx_processor = NULL;
	}
	if (registered_bits & WAVEFRONT_REGISTERED) {
		snd_hwdep_unregister(wavefront_synth);
		wavefront_synth = NULL;
	}
	if (registered_bits & OPL3_REGISTERED) {
		snd_hwdep_unregister(fm_synth);
		fm_synth = NULL;
	}
	if (registered_bits & MIXER_REGISTERED) {
		snd_mixer_unregister(mixer);
		mixer = NULL;
	}
	if (registered_bits & PCM_REGISTERED) {
		snd_pcm_unregister(pcm);
		pcm = NULL;
	}

	if (fx_processor)
		snd_hwdep_free (fx_processor);
	if (wavefront_synth)
		snd_hwdep_free (wavefront_synth);
	if (fm_synth)
		snd_hwdep_free (fm_synth);
	if (cs4232_rmidi)
		snd_rawmidi_free (cs4232_rmidi);
	if (ics2115_internal_rmidi)
		snd_rawmidi_free (ics2115_internal_rmidi);
	if (ics2115_external_rmidi)
		snd_rawmidi_free (ics2115_external_rmidi);
	if (mixer)
		snd_mixer_free (mixer);
	if (pcm)
		snd_pcm_free (pcm);

#ifdef CONFIG_ISAPNP
	snd_wavefront_deactivate (acard);
#endif CONFIG_ISAPNP
	snd_card_free(card);
	return -ENXIO;
}	



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

	for (dev = cards = 0; 
	     dev < SND_CARDS && snd_cs4232_pcm_port[dev] > 0; 
	     dev++) {

		acard = (snd_wavefront_card_t *)
			snd_kcalloc (sizeof (snd_wavefront_card_t), GFP_KERNEL);

		if (!acard)
			continue;

		init_waitqueue_head(&acard->wavefront.interrupt_sleeper);

		if (snd_wavefront_probe (dev, acard) < 0) {
			snd_kfree (acard);
			if (snd_cs4232_pcm_port[dev] == SND_AUTO_PORT) {
				break;
			} else {
				continue;
			}
		}
		wavefront_cards[dev] = acard;
		cards++;
	}
	if (!cards) {
		snd_printk (LOGNAME "No cards found or devices busy\n");
		return -ENODEV;
	}
	return 0;
}

void cleanup_module(void)
{
	int idx;
	snd_wavefront_card_t *acard;

	for (idx = 0; idx < SND_CARDS; idx++) {
		acard = wavefront_cards[idx];
		if (acard) {
			snd_card_unregister (acard->card);
			if (acard->fx_processor)
				snd_hwdep_unregister (acard->fx_processor);
			if (acard->wavefront_synth)
				snd_hwdep_unregister(acard->wavefront_synth);
			if (acard->fm_synth)
				snd_hwdep_unregister(acard->fm_synth);
			if (acard->cs4232_rmidi)
				snd_rawmidi_unregister(acard->cs4232_rmidi);
			if (acard->ics2115_internal_rmidi)
				snd_rawmidi_unregister(acard->ics2115_internal_rmidi);
			if (acard->ics2115_external_rmidi)
				snd_rawmidi_unregister(acard->ics2115_external_rmidi);
			if (acard->mixer)
				snd_mixer_unregister(acard->mixer);
			if (acard->pcm) {
				snd_pcm_unregister(acard->pcm);
			}
			snd_card_free(acard->card);
			snd_kfree (acard);
		}
	}
}
