/*
 *  Driver for generic CS4232/CS4235/CS4236B/CS4237B/CS4238B/CS4239 chips
 *  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/cs4231.h"
#include "../include/mpu401.h"
#include "../include/opl3.h"
#include "../include/initval.h"

#if defined(CS4232)
#define IDENT "CS4232"
#else
#define IDENT "CS4236+"
#undef CS4236
#define CS4236
#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;	/* PnP setup */
int snd_cport[SND_CARDS] = SND_DEFAULT_PORT;	/* PnP setup */
int snd_mpu_port[SND_CARDS] = SND_DEFAULT_PORT;	/* PnP setup */
int snd_fm_port[SND_CARDS] = SND_DEFAULT_PORT;	/* PnP setup */
int snd_sb_port[SND_CARDS] = SND_DEFAULT_PORT;	/* PnP setup */
int snd_irq[SND_CARDS] = SND_DEFAULT_IRQ;	/* 5,7,9,11,12,15 */
int snd_mpu_irq[SND_CARDS] = SND_DEFAULT_IRQ;	/* 9,11,12,15 */
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[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 1};
MODULE_PARM(snd_index, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_index, "Index value for " IDENT " soundcard.");
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SND_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for " IDENT " soundcard.");
MODULE_PARM(snd_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_port, "Port # for " IDENT " driver.");
MODULE_PARM(snd_cport, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_cport, "Control port # for " IDENT " driver.");
MODULE_PARM(snd_mpu_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_mpu_port, "MPU-401 port # for " IDENT " driver.");
MODULE_PARM(snd_fm_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_fm_port, "FM port # for " IDENT " driver.");
MODULE_PARM(snd_sb_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_sb_port, "SB port # for " IDENT " driver (optional).");
MODULE_PARM(snd_irq, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_irq, "IRQ # for " IDENT " driver.");
MODULE_PARM(snd_mpu_irq, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_mpu_irq, "MPU-401 IRQ # for " IDENT " driver.");
MODULE_PARM(snd_dma1, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma1, "DMA1 # for " IDENT " driver.");
MODULE_PARM(snd_dma1_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma1_size, "Size of DMA1 # for " IDENT " driver.");
MODULE_PARM(snd_dma2, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma2, "DMA2 # for " IDENT " driver.");
MODULE_PARM(snd_dma2_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma2_size, "Size of DMA2 # for " IDENT " driver.");
MODULE_PARM(snd_isapnp, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_isapnp, "Enable/disable ISA PnP for " IDENT " driver.");

static struct snd_card_cs4236 {
	snd_irq_t *irqptr;
	snd_irq_t *mpuirqptr;
	snd_dma_t *dma1ptr;
	snd_dma_t *dma2ptr;
	snd_card_t *card;
	snd_pcm_t *pcm;
	snd_kmixer_t *mixer;
	snd_rawmidi_t *rmidi;
	snd_hwdep_t *synth;
	cs4231_t *codec;
	unsigned short pcm_status_reg;
#ifdef CONFIG_ISAPNP
	struct isapnp_dev *wss;
	struct isapnp_dev *ctrl;
	struct isapnp_dev *mpu;
#endif
} *snd_card_cs4236_cards[SND_CARDS] = SND_DEFAULT_PTR;

#ifdef CONFIG_ISAPNP
#ifdef CS4232
static unsigned int snd_card_pnpids[] = {
	/* Turtle Beach TBS-2000 (CS4232) */
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_DEVICE(0x7532),   /* DEVICE */
	(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 */	
	/* Turtle Beach Tropez Plus (CS4232) */
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_DEVICE(0x7632),   /* DEVICE */
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_FUNCTION(0x0000), /* WSS */
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_FUNCTION(0x0010), /* CTRL */
	(ISAPNP_VENDOR('P','N','P')<<16)|ISAPNP_FUNCTION(0xb006), /* MPU */
	/* --- */
	0	/* end */
};
#endif
#ifdef CS4236
static unsigned int snd_card_pnpids[] = {
	/* Genius Sound Maker 3DJ - CS4237B */
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_DEVICE(0x0437),   /* DEVICE */
	(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(0x0003), /* MPU */
	/* some uknown CS4236B */
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_DEVICE(0x0b35),   /* DEVICE */
	(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(0x0003), /* MPU */	
	/* HP 6330 Onboard sound */
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_DEVICE(0x1525),	  /* DEVICE */
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_FUNCTION(0x0100), /* WSS */
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_FUNCTION(0x0110), /* CTRL */
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_FUNCTION(0x0103), /* MPU */
	/* ACER AW37 - CS4235 */
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_DEVICE(0x4236),   /* DEVICE */
	(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(0x0003), /* MPU */
	/* build-in soundcard in EliteGroup P5TX-LA motherboard - CS4237B */
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_DEVICE(0x4237),   /* DEVICE */
	(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(0x0003), /* MPU */
	/* Dell Optiplex GX1 - CS4236B */
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_DEVICE(0x6835),   /* DEVICE */
	(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(0x0003), /* MPU */
	/* Turtle Beach Malibu - CS4237B */
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_DEVICE(0x7537),   /* DEVICE */
	(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(0x0003), /* MPU */
	/* Maxi Sound 16 PnP - CS4236B */
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_DEVICE(0x9836),   /* DEVICE */
	(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(0x0003), /* MPU */
	/* AW37/Pro - CS4235 */
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_DEVICE(0xd925),   /* DEVICE */
	(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(0x0003), /* MPU */
	/* AW35/Pro - CS4237B */
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_DEVICE(0xd937),   /* DEVICE */
	(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(0x0003), /* MPU */
#if 0
	/* CS4235 without MPU401 */
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_DEVICE(0xe825),   /* DEVICE */
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_FUNCTION(0x0100), /* WSS */
	(ISAPNP_VENDOR('C','S','C')<<16)|ISAPNP_FUNCTION(0x0110), /* CTRL */
	0,							  /* MPU */
#endif
	/* --- */
	0	/* end */
};
#endif
#endif

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

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

static snd_pcm_t *snd_card_cs4236_detect(snd_card_t * card,
					 unsigned short port,
					 unsigned short cport,
					 unsigned short mpu_port,
					 unsigned short fm_port,
					 unsigned short sb_port,
					 snd_irq_t *irqptr,
					 snd_dma_t *dma1ptr,
					 snd_dma_t *dma2ptr)
{
	snd_pcm_t *pcm;

	if (snd_register_ioport(card, port, 4, IDENT, NULL) < 0)
		return NULL;
	if (snd_register_ioport(card, cport, 8, IDENT " - control", NULL) < 0) {
		snd_unregister_ioports(card);
		return NULL;
	}
	if (mpu_port != SND_AUTO_PORT) {
		if (snd_register_ioport(card, mpu_port, 2, IDENT " - MPU-401", NULL) < 0) {
			snd_unregister_ioports(card);
			snd_printk("ports for " IDENT " MPU-401 are already used, try another\n");
			return NULL;
		}
	}
	if (fm_port != SND_AUTO_PORT) {
		if (snd_register_ioport(card, fm_port, 4, IDENT " - FM", NULL) < 0) {
			snd_unregister_ioports(card);
			snd_printk("ports for " IDENT " FM are already used, try another\n");
			return NULL;
		}
	}
	if (sb_port != SND_AUTO_PORT) {
		if (snd_register_ioport(card, sb_port, 16, IDENT " - SB", NULL) < 0) {
			snd_unregister_ioports(card);
			snd_printk("ports for " IDENT " SB are already used, try another\n");
			return NULL;
		}
	}
#ifdef CS4236
	pcm = snd_cs4236_new_device(card, port, cport,
				    irqptr, dma1ptr, dma2ptr,
				    CS4231_HW_DETECT3, 0);
#else
	pcm = snd_cs4231_new_device(card, port,
				    irqptr, dma1ptr, dma2ptr,
				    CS4231_HW_DETECT, 0);
#endif
	if (!pcm) {
		snd_unregister_ioports(card);
		return NULL;
	}
	return pcm;
}

static void snd_card_cs4236_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	struct snd_card_cs4236 *acard;
	unsigned char status;

	acard = (struct snd_card_cs4236 *) 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_card_cs4236_midi_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	struct snd_card_cs4236 *acard;

	acard = (struct snd_card_cs4236 *) dev_id;
	if (acard == NULL || acard->rmidi == NULL)
		return;
	snd_mpu401_uart_interrupt(acard->rmidi);
}

static int snd_card_cs4236_resources(int dev, struct snd_card_cs4236 *acard,
				     snd_card_t * card)
{
	static int possible_irqs[] = {-1};
	static int possible_dmas[] = {-1};
	int err;

	if ((err = snd_register_interrupt(card, IDENT,
			snd_irq[dev], SND_IRQ_TYPE_ISA,
			snd_card_cs4236_interrupt, acard,
			possible_irqs, &acard->irqptr)) < 0)
		return err;
	acard->mpuirqptr = NULL;
	if (snd_mpu_port[dev] >= 0 && snd_mpu_port[dev] != SND_AUTO_PORT && snd_mpu_irq[dev] >= 0) {
		if (snd_register_interrupt(card,
				IDENT " - MPU401", snd_mpu_irq[dev],
				SND_IRQ_TYPE_ISA,
				snd_card_cs4236_midi_interrupt,
				acard, possible_irqs, &acard->mpuirqptr) < 0) {
			snd_mpu_port[dev] = SND_AUTO_PORT;
			snd_printk("warning: cannot allocate IRQ for MPU401, midi port is disabled\n");
		}
	} else {
		snd_mpu_port[dev] = SND_AUTO_PORT;
		acard->mpuirqptr = NULL;
	}
	if ((err = snd_register_dma_channel(card,
			IDENT " - DMA1", snd_dma1[dev],
			SND_DMA_TYPE_ISA, snd_dma1_size[dev],
			possible_dmas, &acard->dma1ptr)) < 0)
		return err;
	if (snd_dma2[dev] >= 0 && snd_dma2[dev] < 8) {
		if ((err = snd_register_dma_channel(card,
				IDENT " - DMA2", snd_dma2[dev],
				SND_DMA_TYPE_ISA, snd_dma2_size[dev],
				possible_dmas, &acard->dma2ptr)) < 0)
			return err;
	} else {
		acard->dma2ptr = NULL;
	}
	return 0;
}

#ifdef CONFIG_ISAPNP
static int snd_card_cs4236_isapnp(int dev, struct snd_card_cs4236 *acard)
{
	static int idx = 0;
	static struct isapnp_card *card = NULL;
	struct isapnp_dev *pdev;
	unsigned int tmp;
	
      __again:
      	acard->wss = acard->ctrl = acard->mpu = NULL;
	while ((tmp = snd_card_pnpids[idx]) != 0) {
		card = isapnp_find_card(tmp >> 16, tmp & 0xffff, card);
		if (card)
			break;
		idx += 4;
	}
	if (!card) {
		snd_printdd("isapnp failed for " IDENT "\n");
		return -ENODEV;
	}
	tmp = snd_card_pnpids[idx+1];
	acard->wss = isapnp_find_dev(card, tmp >> 16, tmp & 0xffff, NULL);
	if (!acard->wss) {
		snd_printdd("isapnp failed for " IDENT " (WSS) - %i\n", idx);
		goto __again;		/* maybe we have another config */
	} else if (acard->wss->active) {
		goto __again;
	}
	tmp = snd_card_pnpids[idx+2];
	acard->ctrl = isapnp_find_dev(card, tmp >> 16, tmp & 0xffff, NULL);
	if (!acard->ctrl) {
		snd_printdd("isapnp failed for " IDENT " (CTRL) - %i\n", idx);
		goto __again;		/* maybe we have another config */
	} else if (acard->ctrl->active) {
		goto __again;
	}
	if (snd_mpu_port[dev] > 0 && (tmp = snd_card_pnpids[idx+3]) != 0) {
		acard->mpu = isapnp_find_dev(card, tmp >> 16, tmp & 0xffff, NULL);
		if (!acard->mpu) {
			snd_printdd("isapnp failed for " IDENT " (MPU) - %i\n", idx);
			goto __again;		/* maybe we have another config */
		} else if (acard->mpu->active) {
			goto __again;
		}
	} else {
		snd_mpu_port[dev] = -1;
	}
	/* WSS initialization */
	pdev = acard->wss;
	if (pdev->prepare(pdev) < 0)
		return -EAGAIN;
	if (snd_port[dev] != SND_AUTO_PORT)
		isapnp_resource_change(&pdev->resource[0], snd_port[dev], 4);
	if (snd_fm_port[dev] != SND_AUTO_PORT)
		isapnp_resource_change(&pdev->resource[1], snd_fm_port[dev], 4);
	if (snd_sb_port[dev] != SND_AUTO_PORT)
		isapnp_resource_change(&pdev->resource[2], snd_sb_port[dev], 16);
	if (snd_irq[dev] != SND_AUTO_IRQ)
		isapnp_resource_change(&pdev->irq_resource[0], snd_irq[dev], 1);
	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] < 0 ? 4 : snd_dma2[dev], 1);
	if (pdev->activate(pdev)<0) {
		snd_printk(IDENT " isapnp configure failed (out of resources?)\n");
		return -EBUSY;
	}
	snd_port[dev] = pdev->resource[0].start;
	snd_fm_port[dev] = pdev->resource[1].start;
	snd_sb_port[dev] = pdev->resource[2].start;
	snd_irq[dev] = pdev->irq_resource[0].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_printdd("isapnp WSS: wss port=0x%lx, fm port=0x%lx, sb port=0x%lx\n",
			snd_port[dev], snd_fm_port[dev], snd_sb_port[dev]);
	snd_printdd("isapnp WSS: irq=%i, dma1=%i, dma2=%i\n",
			snd_irq[dev], snd_dma1[dev], snd_dma2[dev]);
	/* CTRL initialization */
	pdev = acard->ctrl;
	if (pdev->prepare(pdev) < 0) {
		acard->wss->deactivate(acard->wss);
		return -EAGAIN;
	}
	if (snd_cport[dev] != SND_AUTO_PORT)
		isapnp_resource_change(&pdev->resource[0], snd_cport[dev], 8);
	if (pdev->activate(pdev)<0) {
		snd_printk(IDENT " isapnp configure failed (out of resources?)\n");
		acard->wss->deactivate(acard->wss);
		return -EBUSY;
	}
	snd_cport[dev] = pdev->resource[0].start;
	snd_printdd("isapnp CTRL: control port=0x%x\n", snd_cport[dev]);
	/* MPU initialization */
	if (snd_mpu_port[dev] > 0) {
		pdev = acard->mpu;
		if (pdev->prepare(pdev) < 0) {
			acard->wss->deactivate(acard->wss);
			acard->ctrl->deactivate(acard->ctrl);
			return -EAGAIN;
		}
		if (snd_mpu_port[dev] != SND_AUTO_PORT)
			isapnp_resource_change(&pdev->resource[0], snd_mpu_port[dev], 2);
		if (snd_mpu_irq[dev] != SND_AUTO_IRQ)
			isapnp_resource_change(&pdev->irq_resource[0], snd_mpu_irq[dev], 1);
		if (pdev->activate(pdev)<0) {
			snd_mpu_port[dev] = SND_AUTO_PORT;
		} else {
			snd_mpu_port[dev] = pdev->resource[0].start;
			snd_mpu_irq[dev] = pdev->irq_resource[0].start;
		}
		snd_printdd("isapnp MPU: port=0x%x, irq=%i\n", snd_mpu_port[dev], snd_mpu_irq[dev]);
	}
	return 0;
}

static void snd_card_cs4236_deactivate(struct snd_card_cs4236 *acard)
{
	if (acard->wss)
		acard->wss->deactivate(acard->wss);
	if (acard->ctrl)
		acard->wss->deactivate(acard->ctrl);
	if (acard->mpu)
		acard->wss->deactivate(acard->mpu);
}
#endif

static int snd_card_cs4236_probe(int dev, struct snd_card_cs4236 *acard)
{
	snd_card_t *card;
	snd_pcm_t *pcm = NULL;
	snd_kmixer_t *mixer = NULL;
	snd_rawmidi_t *rmidi = NULL;
	snd_hwdep_t *synth = NULL;
	cs4231_t *codec;

#ifndef CONFIG_ISAPNP
	if (snd_port[dev] == SND_AUTO_PORT ||
	    snd_cport[dev] == SND_AUTO_PORT) {
		snd_printk("probing for " IDENT " is not supported\n");
		return -ENODEV;
	}
#endif
	card = snd_card_new(snd_index[dev], snd_id[dev],
		       snd_card_cs4236_use_inc, snd_card_cs4236_use_dec);
	if (!card)
		return -ENOMEM;
#ifdef CS4236
	card->type = SND_CARD_TYPE_CS4236;
#endif
#ifdef CS4232
	card->type = SND_CARD_TYPE_CS4232;
#endif
#ifdef CONFIG_ISAPNP
	if (snd_isapnp[dev]) {
		if (snd_card_cs4236_isapnp(dev, acard)<0) {
			snd_printk("isapnp detection failed and probing for " IDENT " is not supported\n");
			snd_card_free(card);
			return -EBUSY;
		}
	} else {
		if (snd_port[dev] == SND_AUTO_PORT ||
		    snd_cport[dev] == SND_AUTO_PORT) {
			snd_card_free(card);
			return -EBUSY;
		}
	}
#endif
	if (snd_card_cs4236_resources(dev, acard, card) < 0) {
		snd_card_free(card);
		return -EBUSY;
	}
	if (snd_mpu_port[dev] < 0)
		snd_mpu_port[dev] = SND_AUTO_PORT;
	if (snd_fm_port[dev] < 0)
		snd_fm_port[dev] = SND_AUTO_PORT;
	if (snd_sb_port[dev] < 0)
		snd_sb_port[dev] = SND_AUTO_PORT;
	pcm = snd_card_cs4236_detect(card, snd_port[dev],
				     snd_cport[dev],
				     snd_mpu_port[dev],
				     snd_fm_port[dev],
				     snd_sb_port[dev],
				     acard->irqptr, acard->dma1ptr,
				     acard->dma2ptr == NULL ?
				     	acard->dma1ptr : acard->dma2ptr);
	if (!pcm) {
		snd_card_free(card);
		return -ENODEV;
	}
	codec = (cs4231_t *) pcm->private_data;
	acard->codec = codec;
	acard->pcm_status_reg = codec->port + 2;

#ifdef CS4236
	mixer = snd_cs4236_new_mixer(pcm, 0);
#else
	mixer = snd_cs4231_new_mixer(pcm, 0);
#endif
	if (!mixer)
		goto __nodev;

	if (snd_fm_port[dev] != SND_AUTO_PORT)
		synth = snd_opl3_new_device(card, snd_fm_port[dev],
					    snd_fm_port[dev] + 2,
					    OPL3_HW_OPL3_CS, -1);

	if (acard->mpuirqptr) {
		rmidi = snd_mpu401_uart_new_device(card, MPU401_HW_CS4232,
						   snd_mpu_port[dev],
						   acard->mpuirqptr->irq);
		if (!rmidi)
			goto __nodev;
		if (snd_rawmidi_register(rmidi, 0) < 0)
			goto __nodev;
	}
	if (synth && snd_hwdep_register(synth, 0) < 0) {
		if (rmidi)
			snd_rawmidi_unregister(rmidi);
		rmidi = NULL;
		goto __nodev;
	}
	if (snd_mixer_register(mixer, 0) < 0) {
		if (synth)
			snd_hwdep_unregister(synth);
		synth = NULL;
		if (rmidi)
			snd_rawmidi_unregister(rmidi);
		rmidi = NULL;
		goto __nodev;
	}
	if (snd_pcm_register(pcm, 0)) {
		if (synth)
			snd_hwdep_unregister(synth);
		synth = NULL;
		if (rmidi)
			snd_rawmidi_unregister(rmidi);
		rmidi = NULL;
		snd_mixer_unregister(mixer);
		mixer = NULL;
		goto __nodev;
	}
#ifdef CS4236
	strcpy(card->abbreviation, "CS4236");
#endif
#ifdef CS4232
	strcpy(card->abbreviation, "CS4232");
#endif
	strcpy(card->shortname, pcm->name);
	sprintf(card->longname, "%s at 0x%x, irq %i, dma %i",
		pcm->name,
		codec->port,
		acard->irqptr->irq,
		acard->dma1ptr->dma);
	if (acard->dma2ptr)
		sprintf(card->longname + strlen(card->longname), "&%i",
			acard->dma2ptr->dma);
	if (!snd_card_register(card)) {
		acard->card = card;
		acard->pcm = pcm;
		acard->mixer = mixer;
		acard->rmidi = rmidi;
		acard->synth = synth;
		return 0;
	}
	snd_hwdep_unregister(synth);
	snd_mixer_unregister(mixer);
	snd_pcm_unregister(pcm);
	snd_card_free(card);
	return -ENOMEM;

      __nodev:
	if (synth)
		snd_hwdep_free(synth);
	if (rmidi)
		snd_rawmidi_free(rmidi);
	if (mixer)
		snd_mixer_free(mixer);
	if (pcm)
		snd_pcm_free(pcm);
	snd_card_free(card);
	return -ENXIO;
}

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

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

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

	for (idx = 0; idx < SND_CARDS; idx++) {
		acard = snd_card_cs4236_cards[idx];
		if (acard) {
			snd_card_unregister(acard->card);
			if (acard->synth)
				snd_hwdep_unregister(acard->synth);
			if (acard->rmidi)
				snd_rawmidi_unregister(acard->rmidi);
			if (acard->mixer)
				snd_mixer_unregister(acard->mixer);
			if (acard->pcm) {
				snd_pcm_unregister(acard->pcm);
			}
			snd_card_free(acard->card);
#ifdef CONFIG_ISAPNP
			snd_card_cs4236_deactivate(acard);
#endif
			snd_kfree(acard);
		}
	}
}
