/*
 *  Driver for generic ESS AudioDrive ES18xx soundcards
 *  Copyright (c) by Christian Fischbach
 *  <fishbach@pool.informatik.rwth-aachen.de>
 *  Copyright (c) by Abramo Bagnara
 *  <abbagnara@racine.ra.it>
 *
 *
 *   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 "es18xx.h"
#include "mpu401.h"
#include "opl3.h"
#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;	/* 0x220,0x240,0x260,0x280 */
#ifndef CONFIG_ISAPNP
int snd_mpu_port[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = -1};
#else
int snd_mpu_port[SND_CARDS] = SND_DEFAULT_PORT;
#endif
int snd_fm_port[SND_CARDS] = SND_DEFAULT_PORT;
int snd_irq[SND_CARDS] = SND_DEFAULT_IRQ;	/* 5,7,9,10 */
int snd_dma1[SND_CARDS] = SND_DEFAULT_DMA;	/* 0,1,3 */
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 */
int snd_dma2_size[SND_CARDS] = SND_DEFAULT_DMA_SIZE;	/* 8,16,32,64,128 */
#ifdef CONFIG_ISAPNP
int snd_skip_isapnp[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 0};
#endif
#ifdef MODULE_PARM
MODULE_PARM(snd_index, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_index, "Index value for ES18xx soundcard.");
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SND_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for ES18xx soundcard.");
MODULE_PARM(snd_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_port, "Port # for ES18xx driver.");
MODULE_PARM(snd_mpu_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_mpu_port, "MPU-401 port # for ES18xx driver.");
MODULE_PARM(snd_fm_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_fm_port, "FM port # for ES18xx driver (optional).");
MODULE_PARM(snd_irq, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_irq, "IRQ # for ES18xx driver.");
MODULE_PARM(snd_dma1, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma1, "DMA 1 # for ES18xx driver.");
MODULE_PARM(snd_dma1_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma1_size, "Size of DMA 1 # for ES18xx driver.");
MODULE_PARM(snd_dma2, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma2, "DMA 2 # for ES18xx driver.");
MODULE_PARM(snd_dma2_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma2_size, "Size of DMA 2 # for ES18xx driver.");
#ifdef CONFIG_ISAPNP
MODULE_PARM(snd_skip_isapnp, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_skip_isapnp, "Skip ISA PnP detection for specified soundcard.");
#endif
#endif

static struct snd_audiodrive {
	snd_irq_t * irqptr;
	snd_dma_t * dma1ptr;
	snd_dma_t * dma2ptr;
	snd_card_t *card;
	snd_pcm_t *pcm_a;
	snd_pcm_t *pcm_b;
	snd_kmixer_t *mixer;
	snd_rawmidi_t *rmidi;
	snd_synth_t *synth;
	es18xx_t *codec;
#ifdef CONFIG_ISAPNP
	struct isapnp_logdev *logdev;
	struct isapnp_logdev *logdevc;
#endif
} *snd_audiodrive_cards[SND_CARDS] = SND_DEFAULT_PTR;

#ifdef CONFIG_ISAPNP
static unsigned int snd_audiodrive_pnpids[] = {
	/* ESS 1868 (integrated on Compaq dual P-Pro motherboard and Genius 18PnP 3D) */
	(ISAPNP_VENDOR('E','S','S')<<16)|ISAPNP_DEVICE(0x1868),	  /* DEVICE */
	(ISAPNP_VENDOR('E','S','S')<<16)|ISAPNP_FUNCTION(0x1868), /* Audio */
	(ISAPNP_VENDOR('E','S','S')<<16)|ISAPNP_FUNCTION(0x0000), /* Control */
	/* ESS 1868 (integrated on Maxisound Cards) */
	(ISAPNP_VENDOR('E','S','S')<<16)|ISAPNP_DEVICE(0x1868),	 /* DEVICE */
	(ISAPNP_VENDOR('E','S','S')<<16)|ISAPNP_FUNCTION(0x8611), /* Audio */
	(ISAPNP_VENDOR('E','S','S')<<16)|ISAPNP_FUNCTION(0x8610), /* Control */
	/* --- */
	/* ESS ES1869 Plug and Play AudioDrive */
	(ISAPNP_VENDOR('E','S','S')<<16)|ISAPNP_DEVICE(0x0003),   /* DEVICE */
	(ISAPNP_VENDOR('E','S','S')<<16)|ISAPNP_FUNCTION(0x1869), /* Audio */
	(ISAPNP_VENDOR('E','S','S')<<16)|ISAPNP_FUNCTION(0x0006), /* Control */
	/* ESS 1869 */
	(ISAPNP_VENDOR('E','S','S')<<16)|ISAPNP_DEVICE(0x1869),   /* DEVICE */
	(ISAPNP_VENDOR('E','S','S')<<16)|ISAPNP_FUNCTION(0x1869), /* Audio */
	(ISAPNP_VENDOR('E','S','S')<<16)|ISAPNP_FUNCTION(0x0006), /* Control */
	/* ESS 1878 */
	(ISAPNP_VENDOR('E','S','S')<<16)|ISAPNP_DEVICE(0x1878),   /* DEVICE */
	(ISAPNP_VENDOR('E','S','S')<<16)|ISAPNP_FUNCTION(0x1878), /* Audio */
	(ISAPNP_VENDOR('E','S','S')<<16)|ISAPNP_FUNCTION(0x0004), /* Control */
	/* ESS 1879 */
	(ISAPNP_VENDOR('E','S','S')<<16)|ISAPNP_DEVICE(0x1879),   /* DEVICE */
	(ISAPNP_VENDOR('E','S','S')<<16)|ISAPNP_FUNCTION(0x1879), /* Audio */
	(ISAPNP_VENDOR('E','S','S')<<16)|ISAPNP_FUNCTION(0x0009), /* Control */
	/* --- */
	0       /* end */
};
#endif

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

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

static es18xx_t *snd_audiodrive_detect(snd_card_t * card,
				       int port,
				       int mpu_port,
				       int fm_port,
				       snd_irq_t * irqptr,
				       snd_dma_t * dma1ptr,
				       snd_dma_t * dma2ptr)
{
	es18xx_t *codec;
	int ctrl_port;

	codec = snd_es18xx_new_device(card, port, mpu_port, fm_port,
				      irqptr, dma1ptr, dma2ptr);
	if(!codec)
		return NULL;

	/* Audio ports registration */
	if(snd_register_ioport(card, port, 16, "ES18xx", NULL) < 0) {
		snd_printk("ports (0x%x-0x%x) for ES18xx are already used\n", port, port+15);
		goto giveup;
	}

	if (codec->caps & ES18XX_CONTROL) {
		/* Control ports registration */
		ctrl_port = codec->ctrl_port;
		if (snd_register_ioport(card, ctrl_port, 8, "ES18xx - CTRL", NULL) < 0) {
			snd_printk("ports (0x%x-0x%x) for ES18xx - CTRL are already used\n", ctrl_port, ctrl_port+7);
			goto giveup;
		}
	}

	/* FM ports registration */
	if (fm_port > SND_AUTO_PORT &&
	    snd_register_ioport(card, fm_port, 4, "ES18xx - FM", NULL) < 0) {
		snd_printk("ports (0x%x-0x%x) for ES18xx - FM are already used\n", fm_port, fm_port+3);
		goto giveup;
	}
	/* MPU ports registration */
	if(mpu_port > SND_AUTO_PORT &&
	   snd_register_ioport(card, mpu_port, 2, "ES18xx - MPU-401", NULL) < 0) {
		snd_printk("ports (0x%x-0x%x) for ES18xx - MPU-401 are already used\n", mpu_port, mpu_port+1);
		goto giveup;
	}
	return codec;

 giveup:
	snd_unregister_ioports(card);
	snd_free(codec, sizeof(es18xx_t));
	return NULL;
}

static void snd_audiodrive_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	unsigned char status;
	struct snd_audiodrive *acard;
	es18xx_t *codec;

	acard = (struct snd_audiodrive *) dev_id;
	if (acard == NULL)
		return;
	codec = (es18xx_t *) acard->codec;
	if (codec == NULL)
		return;

	if (codec->caps & ES18XX_CONTROL) {
		/* Mask all interrupts */
		outb(0, codec->ctrl_port + 7);
		
		/* Read Interrupt status */
		status = inb(codec->ctrl_port + 6);
	}
	else if (1) {
		/* Read Interrupt status */
		status = snd_es18xx_mixer_read(codec, 0x7f) >> 4;
	}
	else {
		status = 0;
		if (inb(codec->port + 0x0C) & 0x01)
			status |= AUDIO1_IRQ;
		if (snd_es18xx_mixer_read(codec, 0x7A) & 0x80)
			status |= AUDIO2_IRQ;
		if ((codec->caps & ES18XX_HWV) &&
		    snd_es18xx_mixer_read(codec, 0x64) & 0x10)
			status |= HWV_IRQ;
	}

	/* Audio 1 & Audio 2 */
	if (status & (AUDIO1_IRQ | AUDIO2_IRQ))
		snd_es18xx_interrupt(codec, status);
	/* MPU */
	if ((status & MPU_IRQ) && acard->rmidi)
		snd_mpu401_uart_interrupt(acard->rmidi);
	/* Hardware volume */
	if (status & HWV_IRQ) {
		int left,right,mute;
		left = snd_es18xx_mixer_read(codec, 0x60);
		right = snd_es18xx_mixer_read(codec, 0x62);
#if 0 /* TODO!!! */
		mute = (((left & 0x40) ? SND_MIX_MUTE_LEFT : 0) |
			((right & 0x40) ? SND_MIX_MUTE_RIGHT : 0));
		left &= 0x3f;
		right &= 0x3f;
		snd_mixer_hardware_volume(acard->mixer, SND_MIXER_PRI_MASTER, 
					  0, SND_MIX_VOL_OUTPUT,
					  left, right, mute);
#endif
		/* ack interrupt */
		snd_es18xx_mixer_write(codec, 0x66, 0x00);
	}

	if (codec->caps & ES18XX_CONTROL) {
		/* Unmask all interrupts */
		outb(0x0f, codec->ctrl_port + 7);
	}
}

static int snd_audiodrive_resources(int dev,
				    struct snd_audiodrive *acard,
				    snd_card_t * card)
{
	static int possible_irqs[] = {5, 9, 10, 7, 11, 12, -1};
	static int possible_dmas[] = {1, 0, 3, -1};
	int err;

	if((err = snd_register_interrupt(card, "ES18xx",
					snd_irq[dev], SND_IRQ_TYPE_ISA,
					snd_audiodrive_interrupt, acard,
					possible_irqs, &acard->irqptr)) < 0)
		return err;
	if((err = snd_register_dma_channel(card, "ES18xx DMA 1",
					snd_dma1[dev], SND_DMA_TYPE_ISA,
					snd_dma1_size[dev],
					possible_dmas, &acard->dma1ptr)) < 0)
		return err;
	if((err = snd_register_dma_channel(card, "ES18xx DMA 2",
					snd_dma2[dev], SND_DMA_TYPE_ISA,
					snd_dma2_size[dev],
					possible_dmas, &acard->dma2ptr)) < 0)
		return err;
	return 0;
}

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

	step = 3;
	idx = 0;
      __again:
      	pdev = NULL;
	while ((tmp = snd_audiodrive_pnpids[idx]) != 0) {
		pdev = isapnp_find_device(tmp >> 16, tmp & 0xffff, dev);
		if (pdev)
			break;
		idx += step;
	}
	if (!pdev) {
		snd_printdd("isapnp failed for ES18xx\n");
		return -ENODEV;
	}
	tmp = snd_audiodrive_pnpids[idx+1];
	acard->logdev = logdev = isapnp_find_logdev(pdev, tmp >> 16, tmp & 0xffff, 0);
	if (!acard->logdev) {
                snd_printdd("isapnp failed for ES18xx - %i\n", idx);
                idx += step;
                goto __again;           /* maybe we have another config */
        }
	tmp = snd_audiodrive_pnpids[idx+2];
	acard->logdevc = isapnp_find_logdev(pdev, tmp >> 16, tmp & 0xffff, 0);
	if (!acard->logdevc) {
                snd_printdd("isapnp failed for ES18xx (Control) - %i\n", idx);
                idx += step;
                goto __again;           /* maybe we have another config */
        }
	if (isapnp_cfg_begin(logdev->dev->csn, acard->logdevc->number))
		return -EAGAIN;
	/* Control port initialization */
	if (isapnp_config_init(&cfg, acard->logdevc)<0) {
		isapnp_cfg_end();
		return -EAGAIN;
	}
	if (isapnp_configure(&cfg) < 0) {
		snd_printk("ES18xx isapnp control configure failure (out of resources?)\n");
		isapnp_cfg_end();
		return -EAGAIN;
	}
	cport = cfg.port[0];
	snd_printdd("isapnp ES18xx: port=0x%x\n", cfg.port[0]);
	/* PnP 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_fm_port[dev] != SND_AUTO_PORT)
		cfg.port[1] = snd_fm_port[dev];
	if (snd_mpu_port[dev] != SND_AUTO_PORT)
		cfg.port[2] = snd_mpu_port[dev];
	if (snd_dma1[dev] != SND_AUTO_DMA)
		cfg.dma[0] = snd_dma1[dev];
	if (snd_dma2[dev] != SND_AUTO_DMA)
		cfg.dma[1] = snd_dma2[dev];
	if (snd_irq[dev] != SND_AUTO_IRQ)
		cfg.irq[0] = snd_irq[dev];
	cfg.port_disable[0] = cport;
	cfg.port_disable_size[0] = 8;
	if (isapnp_configure(&cfg)<0) {
		snd_printk("ES18xx isapnp configure failure (out of resources?)\n");
		isapnp_cfg_end();
		return -EBUSY;
	}
	/* ok. hack using Vendor-Defined Card-Level registers */
	/* skip csn and logdev initialization - already done in isapnp_configure */
	isapnp_cfg_set_byte(0x27, cfg.irq[0]);	/* Hardware Volume IRQ Number */
	if (snd_mpu_port[dev] > SND_AUTO_PORT)
		isapnp_cfg_set_byte(0x28, cfg.irq[0]);	/* MPU-401 IRQ Number */
	isapnp_cfg_set_byte(0x72, cfg.irq[0]);	/* second IRQ */
	snd_printdd("isapnp ES18xx: port=0x%x, fm port=0x%x, mpu port=0x%x\n", cfg.port[0], cfg.port[1], cfg.port[2]);
	snd_printdd("isapnp ES18xx: dma1=%i, dma2=%i, irq=%i\n", cfg.dma[0], cfg.dma[1], cfg.irq[0]);
	snd_port[dev] = cfg.port[0];
	snd_fm_port[dev] = cfg.port[1];
	snd_mpu_port[dev] = cfg.port[2];
	snd_dma1[dev] = cfg.dma[0];
	snd_dma2[dev] = cfg.dma[1];
	snd_irq[dev] = cfg.irq[0];
        isapnp_activate(acard->logdevc->number);
        isapnp_activate(logdev->number);
	isapnp_cfg_end();
	return 0;
}

static void snd_audiodrive_deactivate(struct snd_audiodrive *acard)
{
	if (!acard->logdevc)
		return;
	if (isapnp_cfg_begin(acard->logdevc->dev->csn, acard->logdevc->number)<0)
		return;
	isapnp_deactivate(acard->logdevc->number);
	if (acard->logdev)
		isapnp_deactivate(acard->logdev->number);
	isapnp_cfg_end();
}
#endif /* CONFIG_ISAPNP */

static int snd_audiodrive_probe(int dev, struct snd_audiodrive *acard)
{
#ifndef CONFIG_ISAPNP
	static int possible_ports[] =
		{0x220, 0x240, 0x260, 0x280, -1};
	int *ports = possible_ports;
#endif
	snd_card_t *card;
	snd_pcm_t *pcm_a = NULL;
	snd_pcm_t *pcm_b = NULL;
	snd_kmixer_t *mixer = NULL;
	snd_rawmidi_t *rmidi = NULL;
	snd_synth_t *synth = NULL;
	es18xx_t *codec;

	card = snd_card_new(snd_index[dev], snd_id[dev],
			 snd_audiodrive_use_inc, snd_audiodrive_use_dec);
	if(!card)
		return -ENOMEM;
	card->type = SND_CARD_TYPE_ESS_ES18XX;
#ifdef CONFIG_ISAPNP
	if (!snd_skip_isapnp[dev] && snd_audiodrive_isapnp(dev, acard) < 0) {
		snd_card_free(card);
		return -ENODEV;
	}
#endif
	if(snd_audiodrive_resources(dev, acard, card) < 0) {
		snd_card_free(card);
		return -EBUSY;
	}
	codec = NULL;
#ifndef CONFIG_ISAPNP
	if(snd_port[dev] == SND_AUTO_PORT) {
		for(ports = possible_ports; *ports >= 0; ports++) {
			codec = snd_audiodrive_detect(card, *ports,
				snd_mpu_port[dev], snd_fm_port[dev],
				acard->irqptr, acard->dma1ptr, acard->dma2ptr);
			if(codec)
				break;
		}
		if (!codec) {
			snd_card_free(card);
			return -ENODEV;
		}
	} else {
#endif
		codec = snd_audiodrive_detect(card, snd_port[dev],
			snd_mpu_port[dev], snd_fm_port[dev],
			acard->irqptr, acard->dma1ptr, acard->dma2ptr);
		if (!codec) {
			snd_card_free(card);
			return -ENODEV;
		}
#ifndef CONFIG_ISAPNP
	}
#endif
	pcm_a = snd_es18xx_pcm_a(codec);
	if (!pcm_a)
		goto __nodev;

	if (codec->caps & ES18XX_PCM2) {
		pcm_b = snd_es18xx_pcm_b(codec);
		if (!pcm_b)
			goto __nodev;
	}

	mixer = snd_es18xx_mixer(codec);
	if(!mixer)
		goto __nodev;

	synth = snd_opl3_new_device(card, codec->port, codec->port + 2, OPL3_HW_OPL3, -1);

	if(snd_mpu_port[dev] > SND_AUTO_PORT) {
		snd_printk("ES%x: midi isn't working!!!\n", codec->version);
		rmidi = snd_mpu401_uart_new_device(card, MPU401_HW_ES18XX,
						codec->mpu_port,
						acard->irqptr->irq);
		if(!rmidi)
			goto __nodev;
		if(snd_rawmidi_register(rmidi, 0) < 0)
			goto __nodev;
	}
	if(synth && snd_synth_register(synth) < 0) {
		if(rmidi)
			snd_rawmidi_unregister(rmidi);
		rmidi = NULL;
		goto __nodev;
	}
	if(snd_mixer_register(mixer, 0) < 0) {
		if(synth)
			snd_synth_unregister(synth);
		synth = NULL;
		if(rmidi)
			snd_rawmidi_unregister(rmidi);
		rmidi = NULL;
		goto __nodev;
	}
	if(snd_pcm_register(pcm_a, 0) < 0) {
		if(synth)
			snd_synth_unregister(synth);
		synth = NULL;
		if(rmidi)
			snd_rawmidi_unregister(rmidi);
		rmidi = NULL;
		snd_mixer_unregister(mixer);
		mixer = NULL;
		goto __nodev;
	}
	if (pcm_b && snd_pcm_register(pcm_b, 1) < 0) {
		if (synth)
			snd_synth_unregister(synth);
		synth = NULL;
		if (rmidi)
			snd_rawmidi_unregister(rmidi);
		rmidi = NULL;
		snd_mixer_unregister(mixer);
		mixer = NULL;
		snd_pcm_unregister(pcm_a);
		pcm_a = NULL;
		goto __nodev;
	}
	sprintf(card->abbreviation, "ES%x", codec->version);
	sprintf(card->shortname, "ESS AudioDrive ES%x", codec->version);
	sprintf(card->longname, "%s at 0x%x, irq %i, dma1 %i, dma2 %i",
		card->shortname,
		codec->port,
		acard->irqptr->irq,
		acard->dma1ptr->dma,
		acard->dma2ptr->dma);
	if(!snd_card_register(card)) {
		acard->card = card;
		acard->pcm_a = pcm_a;
		acard->pcm_b = pcm_b;
		acard->mixer = mixer;
		acard->rmidi = rmidi;
		acard->synth = synth;
		acard->codec = codec;
		return 0;
	}
	if (synth)
		snd_synth_unregister(synth);
	if (rmidi)
		snd_rawmidi_unregister(rmidi);
	if (mixer)
		snd_mixer_unregister(mixer);
	if (pcm_a)
		snd_pcm_unregister(pcm_a);
	if (pcm_b)
		snd_pcm_unregister(pcm_b);
	snd_card_free(card);
	return -ENOMEM;

      __nodev:
	if(synth)
		snd_synth_free(synth);
	if(rmidi)
		snd_rawmidi_free(rmidi);
	if(mixer)
		snd_mixer_free(mixer);
	if(pcm_a)
		snd_pcm_free(pcm_a);
	if(pcm_b)
		snd_pcm_free(pcm_b);
	snd_card_free(card);
	return -ENXIO;
}

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

#ifndef LINUX_2_1
	register_symtab(NULL);
#endif
	for(dev = cards = 0; dev < SND_CARDS && snd_port[dev] > 0; dev++) {
		acard = (struct snd_audiodrive *)
				snd_malloc(sizeof(struct snd_audiodrive));
		if(!acard)
			continue;
		memset(acard, 0, sizeof(struct snd_audiodrive));
		if(snd_audiodrive_probe(dev, acard) < 0) {
#ifdef CONFIG_ISAPNP
			snd_audiodrive_deactivate(acard);
#endif
			snd_free(acard, sizeof(struct snd_audiodrive));
			if(snd_port[dev] == SND_AUTO_PORT)
				break;
			snd_printk("ESS AudioDrive ES18xx soundcard #%i not found at 0x%x or device busy\n",
				dev + 1, snd_port[dev]);
			continue;
		}
		snd_audiodrive_cards[dev] = acard;
		cards++;
	}
	if(!cards) {
		snd_printk("ESS AudioDrive ES18xx soundcard #%i not found or device busy\n",
			dev + 1);
		return -ENODEV;
	}
	return 0;
}

void cleanup_module(void)
{
	int idx;
	struct snd_audiodrive *acard;

	for(idx = 0; idx < SND_CARDS; idx++) {
		acard = snd_audiodrive_cards[idx];
		if(acard) {
			snd_card_unregister(acard->card);
			if(acard->synth)
				snd_synth_unregister(acard->synth);
			if(acard->rmidi)
				snd_rawmidi_unregister(acard->rmidi);
			if(acard->mixer)
				snd_mixer_unregister(acard->mixer);
			if(acard->pcm_a)
				snd_pcm_unregister(acard->pcm_a);
			if(acard->pcm_b)
				snd_pcm_unregister(acard->pcm_b);
			if (acard->codec) {
				snd_free(acard->codec, sizeof(es18xx_t));
				acard->codec = NULL;
			}
			snd_card_free(acard->card);
#ifdef CONFIG_ISAPNP
			snd_audiodrive_deactivate(acard);
#endif
			snd_free(acard, sizeof(struct snd_audiodrive));
		}
	}
}
