/*
 *  Driver for Yamaha OPL3-SA[2,3] soundcards
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.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 SNDCFG_ISAPNP
#include "driver.h"
#include "cs4231.h"
#include "mpu401.h"
#include "opl3.h"
#include "initval.h"

#ifdef __QNXNTO__
int snd_index[SND_CARDS] = SND_DEFAULT_IDX;	/* Index 1-MAX */
char *snd_id[SND_CARDS] = SND_DEFAULT_STR;	/* ID for this card */
#ifdef SNDCFG_ISAPNP
int snd_port[SND_CARDS] = { [0 ... (SND_CARDS-1)] = SND_AUTO_PORT }; //SND_DEFAULT_PORT;	/* 0xf86,0x370,0x100 */
int snd_sb_port[SND_CARDS] = SND_DEFAULT_PORT;	/* 0x220,0x240,0x260 */
int snd_wss_port[SND_CARDS] = SND_DEFAULT_PORT;	/* 0x530,0xe80,0xf40,0x604 */
int snd_fm_port[SND_CARDS] = SND_DEFAULT_PORT;	/* 0x388 */
int snd_midi_port[SND_CARDS] = SND_DEFAULT_PORT;/* 0x330,0x300 */
int snd_irq[SND_CARDS] = SND_DEFAULT_IRQ;	/* 0,1,3,5,9,11,12,15 */
int snd_dma1[SND_CARDS] = SND_DEFAULT_DMA;	/* 1,3,5,6,7 */
int snd_dma2[SND_CARDS] = SND_DEFAULT_DMA;	/* 1,3,5,6,7 */
int snd_dma1_size[SND_CARDS] = SND_DEFAULT_DMA_SIZE;	/* 8,16,32,64,128 */
int snd_dma2_size[SND_CARDS] = SND_DEFAULT_DMA_SIZE;	/* 8,16,32,64,128 */
#else
int snd_port[SND_CARDS] = {0x538, -1}; // SND_DEFAULT_PORT;	/* 0xf86,0x370,0x100 */
int snd_sb_port[SND_CARDS] = {0x220, -1}; //SND_DEFAULT_PORT;	/* 0x220,0x240,0x260 */
int snd_wss_port[SND_CARDS] = {0x530, -1}; //SND_DEFAULT_PORT;	/* 0x530,0xe80,0xf40,0x604 */
int snd_fm_port[SND_CARDS] = {0x388, -1}; //SND_DEFAULT_PORT;	/* 0x388 */
int snd_midi_port[SND_CARDS] = {0x330, -1}; //SND_DEFAULT_PORT;/* 0x330,0x300 */
int snd_irq[SND_CARDS] = {5, -1}; //SND_DEFAULT_IRQ;	/* 0,1,3,5,9,11,12,15 */
int snd_dma1[SND_CARDS] = {0, -1}; //SND_DEFAULT_DMA;	/* 1,3,5,6,7 */
int snd_dma2[SND_CARDS] = {1, -1}; //SND_DEFAULT_DMA;	/* 1,3,5,6,7 */
int snd_dma1_size[SND_CARDS] = SND_DEFAULT_DMA_SIZE;	/* 8,16,32,64,128 */
int snd_dma2_size[SND_CARDS] = SND_DEFAULT_DMA_SIZE;	/* 8,16,32,64,128 */
#endif

#ifdef SNDCFG_ISAPNP
int snd_isapnp[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 1};
#endif

#else   /* QNX4 */
int snd_index[SND_CARDS] = {-1,-1,-1,-1,-1,-1,-1,-1};	/* Index 1-MAX */
char *snd_id[SND_CARDS];	/* ID for this card */
#ifdef SNDCFG_ISAPNP
int snd_port[SND_CARDS] = {0x370,0x100,0xf86 }; //SND_DEFAULT_PORT;	/* 0x370,0x100,0xf86 */
int snd_sb_port[SND_CARDS] = {0x220,0x240,0x260};	/* 0x220,0x240,0x260 */
int snd_wss_port[SND_CARDS] = {0x530,0xe80,0xf40,0x604};	/* 0x530,0xe80,0xf40,0x604 */
int snd_fm_port[SND_CARDS] = {0x388};	/* 0x388 */
int snd_midi_port[SND_CARDS] = {0x330,0x300};/* 0x330,0x300 */
int snd_irq[SND_CARDS] = {5,9,11,12,15,0,1,3};	/* 0,1,3,5,9,11,12,15 */
int snd_dma1[SND_CARDS] = {1,3,5,6,7};	/* 1,3,5,6,7 */
int snd_dma2[SND_CARDS] = {1,3,5,6,7};	/* 1,3,5,6,7 */
int snd_dma1_size[SND_CARDS] = {128,128,128,128,128,128,128,128};	
int snd_dma2_size[SND_CARDS] = {128,128,128,128,128,128,128,128};	
#else
int snd_port[SND_CARDS] = {0x370, -1}; // SND_DEFAULT_PORT;	/* 0xf86,0x370,0x100 */
int snd_sb_port[SND_CARDS] = {0x220, -1}; //SND_DEFAULT_PORT;	/* 0x220,0x240,0x260 */
int snd_wss_port[SND_CARDS] = {0x530, -1}; //SND_DEFAULT_PORT;	/* 0x530,0xe80,0xf40,0x604 */
int snd_fm_port[SND_CARDS] = {0x388, -1}; //SND_DEFAULT_PORT;	/* 0x388 */
int snd_midi_port[SND_CARDS] = {0x330, -1}; //SND_DEFAULT_PORT;/* 0x330,0x300 */
int snd_irq[SND_CARDS] = {5, -1}; //SND_DEFAULT_IRQ;	/* 0,1,3,5,9,11,12,15 */
int snd_dma1[SND_CARDS] = {0, -1}; //SND_DEFAULT_DMA;	/* 1,3,5,6,7 */
int snd_dma2[SND_CARDS] = {1, -1}; //SND_DEFAULT_DMA;	/* 1,3,5,6,7 */
int snd_dma1_size[SND_CARDS] = {128,128,128,128,128,128,128,128};
int snd_dma2_size[SND_CARDS] = {128,128,128,128,128,128,128,128};
#endif
#ifdef SNDCFG_ISAPNP
int snd_isapnp[SND_CARDS] = {1,1,1,1,1,1,1,1};
#endif
#endif

struct snd_opl3sa {
	int version;		/* 2 or 3 */
	snd_irq_t *irqptr;
	snd_dma_t *dma1ptr;
	snd_dma_t *dma2ptr;
	unsigned short port;
	snd_card_t *card;
	snd_pcm_t *pcm;
	snd_kmixer_t *mixer;
	snd_rawmidi_t *rawmidi;
	snd_synth_t *synth;
	unsigned short pcm_status_reg;
	snd_mutex_define(reg);
#ifdef SNDCFG_ISAPNP
    struct isapnp_logdev *logdev;
#endif
};

#ifdef __QNXNTO__
static struct snd_opl3sa *snd_opl3sa_cards[SND_CARDS] = SND_DEFAULT_PTR;
#else
static struct snd_opl3sa *snd_opl3sa_cards[SND_CARDS];
#endif

#ifdef SNDCFG_ISAPNP
static unsigned int snd_opl3sa_pnpids[] = {
	/* Yamaha YMF719E-S (Genius Sound Maker 3DX) */
	(ISAPNP_VENDOR('Y','M','H')<<16)|ISAPNP_DEVICE(0x0020),   /* DEVICE */
	(ISAPNP_VENDOR('Y','M','H')<<16)|ISAPNP_FUNCTION(0x0021), /* all */
	0, /* #found */
	/* Yamaha OPL3-SA3 (integrated on Intel's Pentium II AL440LX motherboard) */
	(ISAPNP_VENDOR('Y','M','H')<<16)|ISAPNP_DEVICE(0x0030),   /* DEVICE */
	(ISAPNP_VENDOR('Y','M','H')<<16)|ISAPNP_FUNCTION(0x0021), /* all */
	0, /* #found */
	/* ??? */
	(ISAPNP_VENDOR('Y','M','H')<<16)|ISAPNP_DEVICE(0x0800),   /* DEVICE */
	(ISAPNP_VENDOR('Y','M','H')<<16)|ISAPNP_FUNCTION(0x0021), /* all */
	0, /* #found */
	(ISAPNP_VENDOR('Y','M','H')<<16)|ISAPNP_DEVICE(0x0801),   /* DEVICE */
	(ISAPNP_VENDOR('Y','M','H')<<16)|ISAPNP_FUNCTION(0x0021), /* all */
	0, /* #found */
	/* --- */
	0       /* end */
};
#endif
											
static unsigned char snd_opl3sa_read(unsigned short port, unsigned char reg)
{
	unsigned char result;

	outb(reg, port);	/* register */
	result = inb(port + 1);
#if 0
	snd_printk("read [0x%x] = 0x%x [0x%x]\n", port, result, inb(port));
#endif
	return result;
}

static void snd_opl3sa_write(unsigned short port,
			     unsigned char reg, unsigned char value)
{

	outb(reg, port);	/* register */
	outb(value, port + 1);
}

static int snd_opl3sa_detect(struct snd_opl3sa *oplcard,
			     unsigned short port,
			     unsigned short wss_port,
			     unsigned short sb_port,
			     unsigned short fm_port,
			     unsigned short midi_port)
{
	snd_card_t *card;
	unsigned char tmp, tmp1;
	char str[2];

	card = oplcard->card;
	if (snd_register_ioport(card, port, 2, "OPL3-SA control", NULL) < 0)
		return -EBUSY;
	if (snd_register_ioport(card, wss_port, 8, "OPL3-SA WSS", NULL) < 0)
		goto __nodev;
	if (sb_port >= 0x200 && sb_port < 0x300)
		if (snd_register_ioport(card, sb_port, 16, "OPL3-SA SB", NULL) < 0)
			goto __nodev;
	if (fm_port >= 0x340 && fm_port < 0x400)
		if (snd_register_ioport(card, fm_port, 8, "OPL3-SA AdLib FM", NULL) < 0)
			goto __nodev;
	if (midi_port >= 0x300 && midi_port < 0x340)
		if (snd_register_ioport(card, midi_port, 2, "OPL3-SA MPU-401", NULL) < 0)
			goto __nodev;
#if 0
	snd_printk("REG 0A = 0x%x\n", snd_opl3sa_read(port, 0x0a));
#endif
	oplcard->port = port;
	oplcard->version = 0;
	tmp = snd_opl3sa_read(port, 0x0a);
	switch (tmp & 0x07) {
	case 0x01:
		oplcard->version = 2;
		break;
	case 0x02:
	case 0x03:		/* YM715B ?? */
	case 0x04:		/* YM719 - OPL-SA4? */
	case 0x05:		/* OPL3-SA3 - Libretto 100 */
		oplcard->version = 3;
		break;
	default:
		snd_printd("OPL3-SA [0x%x] detect (0) = 0x%x\n", port, tmp);
		goto __nodev;
	}
	str[0] = oplcard->version + '0';
	str[1] = 0;
	strcat(card->shortname, str);
	snd_opl3sa_write(port, 0x0a, tmp | 7);
	if ((tmp1 = snd_opl3sa_read(port, 0x0a)) != tmp) {
		snd_printd("OPL3-SA [0x%x] detect (1) = 0x%x (0x%x)\n", port, tmp, tmp1);
		goto __nodev;
	}
	snd_opl3sa_write(port, 0x01, 0x00);	/* Power Management - default */
	snd_opl3sa_write(port, 0x02, 0x00);	/* System Control - default */
	snd_opl3sa_write(port, 0x03, 0x0d);	/* Interrupt Channel Configuration - IRQ A = OPL3 + MPU + WSS */
	if (oplcard->dma2ptr == NULL) {
		snd_opl3sa_write(port, 0x06, 0x03);	/* DMA Configuration - DMA A = WSS-R + WSS-P */
	} else {
		snd_opl3sa_write(port, 0x06, 0x21);	/* DMA Configuration - DMA B = WSS-R, DMA A = WSS-P */
	}
	snd_opl3sa_write(port, 0x0a, 0x80 | (tmp & 7));	/* Miscellaneous - default */
	if (oplcard->version > 2) {
		snd_opl3sa_write(port, 0x12, 0x00);	/* Digital Block Partial Power Down - default */
		snd_opl3sa_write(port, 0x13, 0x00);	/* Analog Block Partial Power Down - default */
	}
	return 0;

      __nodev:
	snd_unregister_ioports(card);
	return -ENODEV;
}

static void snd_opl3sa_interrupt(void *data)
{
	register unsigned short status, pcm_status;
	struct snd_opl3sa *oplcard = (struct snd_opl3sa *) data;
	unsigned char left, right;

	if (!oplcard || !oplcard->card)
		return;


	snd_mutex_lock(oplcard, reg);
	outb(0x04, oplcard->port);	/* register - Interrupt IRQ-A status */
	status = inb(oplcard->port + 1);
	snd_mutex_unlock(oplcard, reg);

	if (oplcard->synth && (status & 0x20))
		snd_opl3_interrupt(oplcard->synth);

	if (oplcard->rawmidi && (status & 0x10))
		snd_mpu401_uart_interrupt(oplcard->rawmidi);

	if (oplcard->pcm && (status & 0x07)) {	/* TI,CI,PI */
		pcm_status = inb(oplcard->pcm_status_reg);
		if (pcm_status & 0x01)
			snd_cs4231_interrupt(oplcard->pcm, pcm_status);
	}

	if (status & 0x40) {
		/* reading from Master Lch register at 0x07 clears this bit */
		snd_mutex_lock(oplcard, reg);
		right = snd_opl3sa_read(oplcard->port, 0x08);
		left = snd_opl3sa_read(oplcard->port, 0x07);
		snd_mutex_unlock(oplcard, reg);
		snd_mixer_hardware_volume(oplcard->mixer, SND_MIXER_PRI_MASTER,
					  0, SND_MIX_VOL_OUTPUT,
					  left & 0x0f, right & 0x0f,
					  (left & 0x80 ? SND_MIX_MUTE_LEFT : 0) |
					  (right & 0x80 ? SND_MIX_MUTE_RIGHT : 0));
	}
}

static int snd_opl3sa_resources(int dev, struct snd_opl3sa *oplcard, snd_card_t * card)
{
	static int possible_irqs[] = {-1}; /* must be specified by user */
	static int possible_dmas[] = {-1}; /* must be specified by user */
	int err;

	if ((err = snd_register_interrupt(card,
			"OPL3-SA", snd_irq[dev], SND_IRQ_TYPE_ISA,
			snd_opl3sa_interrupt, oplcard,
			possible_irqs, &oplcard->irqptr)) < 0)
		return err;
	if ((err = snd_register_dma_channel(card,
			"OPL3-SA playback", snd_dma1[dev], SND_DMA_TYPE_ISA,
			snd_dma1_size[dev], possible_dmas, &oplcard->dma1ptr )) < 0)
		return err;
	if (snd_dma2[dev] >= 0) {
		if ((err = snd_register_dma_channel(card,
				"OPL3-SA record", snd_dma2[dev],
				SND_DMA_TYPE_ISA, snd_dma2_size[dev],
				possible_dmas, &oplcard->dma2ptr)) < 0)
			return err;
	} else {
		oplcard->dma2ptr = NULL;
	}
	return 0;
}

static int snd_opl3sa_set_mute(snd_kmixer_t * mixer,
			       snd_kmixer_channel_t * channel,
			       unsigned int mute)
{
	struct snd_opl3sa *oplcard;
	unsigned char regl, regr, mask, tmp;

	oplcard = (struct snd_opl3sa *) channel->private_data;
	regl = (unsigned char) (channel->hw.private_value >> 24);
	regr = (unsigned char) (channel->hw.private_value >> 16);
	mask = (unsigned char) channel->hw.private_value;
	snd_mutex_lock(oplcard, reg);
	tmp = snd_opl3sa_read(oplcard->port, regl) & ~mask;
	tmp |= mute & SND_MIX_MUTE_LEFT ? mask : 0;
	snd_opl3sa_write(oplcard->port, regl, tmp);
	if (channel->hw.ocaps & SND_MIXER_CINFO_DCAP_STEREO) {
		tmp = snd_opl3sa_read(oplcard->port, regr) & ~mask;
		tmp |= mute & SND_MIX_MUTE_RIGHT ? mask : 0;
		snd_opl3sa_write(oplcard->port, regr, tmp);
	}
	snd_mutex_unlock(oplcard, reg);
	return 0;
}

static int snd_opl3sa_set_volume_level(snd_kmixer_t * mixer,
				       snd_kmixer_channel_t * channel,
				       int left, int right)
{
	struct snd_opl3sa *oplcard;
	unsigned char regl, regr, mask, tmp;

	oplcard = (struct snd_opl3sa *) channel->private_data;
	regl = (unsigned char) (channel->hw.private_value >> 24);
	regr = (unsigned char) (channel->hw.private_value >> 16);
	mask = (unsigned char) channel->hw.private_value;
	if (channel->hw.private_value & 0x2000) {
		left = channel->hw.omax - left;
		right = channel->hw.omax - right;
	}
	snd_mutex_lock(oplcard, reg);
	tmp = snd_opl3sa_read(oplcard->port, regl) & mask;
	snd_opl3sa_write(oplcard->port, regl, tmp | left);
	if (channel->hw.ocaps & SND_MIXER_CINFO_DCAP_STEREO) {
		tmp = snd_opl3sa_read(oplcard->port, regr) & mask;
		snd_opl3sa_write(oplcard->port, regr, tmp | right);
	}
	snd_mutex_unlock(oplcard, reg);
	return 0;
}

static int snd_opl3sa_set_3d_volume_level(snd_kmixer_t * mixer,
					  snd_kmixer_channel_t * channel,
					  int left, int right)
{
	struct snd_opl3sa *oplcard;
	unsigned char reg;

	oplcard = (struct snd_opl3sa *) channel->private_data;
	reg = (unsigned char) channel->hw.private_value;
	snd_mutex_lock(oplcard, reg);
	snd_opl3sa_write(oplcard->port, reg, right << 4 | left);
	snd_mutex_unlock(oplcard, reg);
	return 0;
}

static int snd_opl3sa_control(snd_kmixer_t * mixer,
			      snd_kmixer_channel_t * channel,
			      unsigned int ctrl, long val)
{
	switch (ctrl) {
	case SND_MIX_CTRL_MUTE_OUT:
		return snd_opl3sa_set_mute(mixer, channel, val);
	}
	return -EINVAL;
}

static int snd_opl3sa_volume(snd_kmixer_t * mixer,
			     snd_kmixer_channel_t * channel,
			     unsigned int type, int left, int right)
{
	switch (type) {
	case SND_MIX_VOL_OUTPUT:
		return snd_opl3sa_set_volume_level(mixer, channel, left, right);
	}
	return -EINVAL;
}

static int snd_opl3sa_3d_volume(snd_kmixer_t * mixer,
				snd_kmixer_channel_t * channel,
				unsigned int type, int left, int right)
{
	switch (type) {
	case SND_MIX_VOL_OUTPUT:
		return snd_opl3sa_set_3d_volume_level(mixer, channel, left, right);
	}
	return -EINVAL;
}

#define OPL3SA_PRIVATE( left, right, shift, mute ) \
			((left << 24)|(right << 16)|(shift<<8)|mute)

static int snd_opl3sa_mixer(struct snd_opl3sa *oplcard, snd_kmixer_t * mixer)
{
	static struct snd_stru_mixer_channel_hw master_mix =
	{
		SND_MIXER_PRI_MASTER,	/* priority */
		SND_MIXER_PRI_PARENT,	/* parent priority */
		SND_MIXER_ID_MASTER,	/* device name */
		SND_MIXER_OSS_VOLUME,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME |
		SND_MIXER_CINFO_DCAP_HWMUTE,
		/* Input capabilities */
		0,
		0, 15,			/* omin, omax value */
		-3000, 0,		/* omin, omax dB */
        0, 0,			/* imin, imax value */
        0, 0,			/* imin, imax dB */
		snd_opl3sa_control,	/* set control */
		snd_opl3sa_volume,	/* set volume level*/
		OPL3SA_PRIVATE(0x07, 0x08, 0, 0x80) | 0x2000	/* private value */
	};
	static struct snd_stru_mixer_channel_hw wide_mix =
	{
		SND_MIXER_PRI_3D,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_3D,	/* device name */
		SND_MIXER_OSS_UNKNOWN,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_EFFECT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME,
		/* Input capabilities */
		0,
		0, 7,			/* omin, omax value */
		0, 1050,		/* omin, omax dB */
        0, 0,			/* imin, imax value */
        0, 0,			/* imin, imax dB */
		0,			/* set control */
		snd_opl3sa_3d_volume,	/* set volume level*/
		0x14			/* private value */
	};
	static struct snd_stru_mixer_channel_hw bass_mix =
	{
		SND_MIXER_PRI_BASS,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_BASS,	/* device name */
		SND_MIXER_OSS_BASS,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_EFFECT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME,
		/* Input capabilities */
		0,
		0, 7,			/* omin, omax value */
		0, 1050,		/* omin, omax dB */
                0, 0,			/* imin, imax value */
                0, 0,			/* imin, imax dB */
		0,			/* set control */
		snd_opl3sa_3d_volume,	/* set volume level*/
		0x15			/* private value */
	};
	static struct snd_stru_mixer_channel_hw treble_mix =
	{
		SND_MIXER_PRI_TREBLE,	/* priority */
		SND_MIXER_PRI_MASTER,	/* parent priority */
		SND_MIXER_ID_TREBLE,	/* device name */
		SND_MIXER_OSS_TREBLE,	/* OSS device # */
		/* General capabilities */
		SND_MIXER_CINFO_CAP_OUTPUT | SND_MIXER_CINFO_CAP_EFFECT,
		/* Output capabilities */
		SND_MIXER_CINFO_DCAP_STEREO | SND_MIXER_CINFO_DCAP_VOLUME,
		/* Input capabilities */
		0,
		0, 7,			/* omin, omax value */
		0, 1050,		/* omin, omax dB */
                0, 0,			/* imin, imax value */
                0, 0,			/* imin, imax dB */
		0,			/* set control */
		snd_opl3sa_3d_volume,	/* set volume level*/
		0x16			/* private value */
	};
	snd_kmixer_channel_t *channel, *channel1, *channel2, *channel3;

	/* reassign AUXA to CD */
	channel1 = snd_mixer_find_channel(mixer, SND_MIXER_PRI_AUXA);
	channel1->hw.priority = SND_MIXER_PRI_CD;
	strcpy(channel1->hw.name, SND_MIXER_ID_CD);
	channel1->hw.ossdev = SND_MIXER_OSS_CD;
	snd_mixer_reorder_channel(mixer, channel1);
	/* reassign AUXB to FM */
	channel2 = snd_mixer_find_channel(mixer, SND_MIXER_PRI_AUXB);
	channel2->hw.priority = SND_MIXER_PRI_FM;
	strcpy(channel2->hw.name, SND_MIXER_ID_FM);
	channel2->hw.ossdev = SND_MIXER_OSS_SYNTH;
	channel2->hw.caps &= ~SND_MIXER_CINFO_CAP_EXTINPUT;
	snd_mixer_reorder_channel(mixer, channel2);
	/* reassign MONO to MIC */
	channel3 = snd_mixer_find_channel(mixer, SND_MIXER_PRI_MONO);
	channel3->hw.priority = SND_MIXER_PRI_MIC;
	strcpy(channel3->hw.name, SND_MIXER_ID_MIC);
	channel3->hw.caps |= SND_MIXER_CINFO_CAP_EXTINPUT;
	channel3->hw.omax = 31;
	channel3->hw.omin_dB = -3450;
	channel3->hw.omax_dB = 1200;
	channel3->hw.private_value = OPL3SA_PRIVATE(0x09, 0x00, 0, 0x80) | 0x2000;
	channel3->hw.set_control = snd_opl3sa_control;
	channel3->hw.set_volume_level = snd_opl3sa_volume;
	channel3->private_data = oplcard;
	snd_mixer_reorder_channel(mixer, channel3);
	/* add Master volume control */
	channel = snd_mixer_new_channel(mixer, &master_mix);
	if (!channel)
		return -ENOMEM;
	channel->private_data = oplcard;
	/* add 3D Wide, Bass and Treble for OPL3-SA3 */
	if (oplcard->version > 2) {
		channel = snd_mixer_new_channel(mixer, &wide_mix);
		if (channel)
			channel->private_data = oplcard;
		channel = snd_mixer_new_channel(mixer, &bass_mix);
		if (channel)
			channel->private_data = oplcard;
		channel = snd_mixer_new_channel(mixer, &treble_mix);
		if (channel)
			channel->private_data = oplcard;
	}
	return 0;
}

#ifdef SNDCFG_ISAPNP
static int snd_opl3sa_isapnp(int dev, struct snd_opl3sa *oplcard)
{
        int idx, step;
        unsigned int tmp;
	struct isapnp_dev *pdev;
	struct isapnp_logdev *logdev;
	struct isapnp_config cfg;

	step = 3;
	idx = 0;
      __again:
      	pdev = NULL;
	while ((tmp = snd_opl3sa_pnpids[idx]) != 0) {
		pdev = isapnp_find_device(tmp >> 16, tmp & 0xffff, snd_opl3sa_pnpids[idx+2]);
		if (pdev)
			break;
		idx += step;
	}
	if (!pdev) {
		snd_printdd("isapnp failed for OPL3-SA\n");
		return -ENODEV;
	}
	tmp = snd_opl3sa_pnpids[idx+1];
	oplcard->logdev = logdev = isapnp_find_logdev(pdev, tmp >> 16, tmp & 0xffff, 0);
	if (!oplcard->logdev) {
                snd_printdd("isapnp failed for OPL3-SA - %i\n", idx);
                idx += step;
                goto __again;           /* maybe we have another config */
        }
   	if (isapnp_cfg_begin(logdev->dev->csn, logdev->number))
		return -EAGAIN;
	/* PnP initialization */
	if (isapnp_config_init(&cfg, logdev)) {
		isapnp_cfg_end();
		return -EAGAIN;
	}
	if (snd_sb_port[dev] != SND_AUTO_PORT)
		cfg.port[0] = snd_sb_port[dev];
	if (snd_wss_port[dev] != SND_AUTO_PORT)
		cfg.port[1] = snd_wss_port[dev];
	if (snd_fm_port[dev] != SND_AUTO_PORT)
		cfg.port[2] = snd_fm_port[dev];
	if (snd_midi_port[dev] != SND_AUTO_PORT)
		cfg.port[3] = snd_midi_port[dev];
	if (snd_port[dev] != SND_AUTO_PORT)
		cfg.port[4] = snd_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];
	if (isapnp_configure(&cfg)<0) {
		snd_printk("OPL3-SA isapnp configure failure (out of resources?)\n");
		isapnp_cfg_end();
		return -EBUSY;
	}
	snd_opl3sa_pnpids[idx+2]++;
	snd_printk("isapnp OPL3-SA: sb port=0x%x, wss port=0x%x, fm port=0x%x, midi port=0x%x\n", cfg.port[0], cfg.port[1], cfg.port[2], cfg.port[3]);
	snd_printk("isapnp OPL3-SA: control port=0x%x, dma1=%i, dma2=%i, irq=%i\n", cfg.port[4], cfg.dma[0], cfg.dma[1], cfg.irq[0]);
	snd_sb_port[dev] = cfg.port[0];
	snd_wss_port[dev] = cfg.port[1];
	snd_fm_port[dev] = cfg.port[2];
	snd_midi_port[dev] = cfg.port[3];
	snd_port[dev] = cfg.port[4];
	snd_dma1[dev] = cfg.dma[0];
	snd_dma2[dev] = cfg.dma[1];
	snd_irq[dev] = cfg.irq[0];
        isapnp_activate(logdev->number);
	isapnp_cfg_end();
	return 0;
}

static void snd_opl3sa_deactivate(struct snd_opl3sa *oplcard)
{
	if (!oplcard->logdev)
		return;
	if (isapnp_cfg_begin(oplcard->logdev->dev->csn, oplcard->logdev->number)<0)
		return;
	isapnp_deactivate(oplcard->logdev->number);
	isapnp_cfg_end();
}
#endif /* SNDCFG_ISAPNP */

static int snd_opl3sa_probe(int dev, struct snd_opl3sa *oplcard)
{
	snd_card_t *card;
	snd_pcm_t *pcm;
	snd_kmixer_t *mixer = NULL;
	snd_rawmidi_t *rmidi = NULL;
	snd_synth_t *synth = NULL;

#ifdef SNDCFG_ISAPNP
    if (!snd_isapnp[dev]) {
#endif
		if (snd_port[dev] == SND_AUTO_PORT ||
		    snd_wss_port[dev] == SND_AUTO_PORT ||
		    snd_fm_port[dev] == SND_AUTO_PORT ||
	    	snd_midi_port[dev] == SND_AUTO_PORT) {
			snd_printk("probing for Yamaha OPL3-SA isn't supported\n");
			snd_printk("port = 0x%x, wss_port = 0x%x, fm_port = 0x%x, midi_port = 0x%x\n",
				   snd_port[dev],
				   snd_wss_port[dev],
				   snd_fm_port[dev],
				   snd_midi_port[dev]);
			return -EINVAL;
		}
#ifdef SNDCFG_ISAPNP
    }
#endif	
	card = snd_card_new(snd_index[dev], snd_id[dev]);
	if (!card)
		return -ENOMEM;
	card->type = SND_CARD_TYPE_OPL3_SA;
	strcpy(card->abbreviation, "OPL3SA");
	strcpy(card->shortname, "Yamaha OPL3-SA");
#ifdef SNDCFG_ISAPNP
	if( snd_isapnp[dev] )
	{
		if (snd_opl3sa_isapnp(dev, oplcard) < 0)
		{	snd_card_free(card);
			return -EBUSY;
		}
	}
#endif
	if (snd_opl3sa_resources(dev, oplcard, card) < 0) {
		snd_card_free(card);
		return -EBUSY;
	}
	oplcard->card = card;
	if (snd_opl3sa_detect(oplcard, snd_port[dev], snd_wss_port[dev],
			      snd_sb_port[dev], snd_fm_port[dev],
			      snd_midi_port[dev])) {
		snd_card_free(card);
		oplcard->card = NULL;
		return -ENODEV;
	}
	oplcard->port = snd_port[dev];
	if (snd_card_register(card)) {
		snd_card_free(card);
		return -ENOMEM;
	}
	pcm = snd_cs4231_new_device(card,
				    snd_wss_port[dev] + 4,
				    oplcard->irqptr,
				    oplcard->dma1ptr,
				    oplcard->dma2ptr == NULL ?
				    	oplcard->dma1ptr : oplcard->dma2ptr,
				    CS4231_HW_OPL3SA,
				    0);
	if (!pcm) {
		snd_printd("Oops, WSS not detected at 0x%x\n", snd_wss_port[dev] + 4);
		goto __nodev;
	}
	oplcard->pcm_status_reg = snd_wss_port[dev] + 4 + 2;
	mixer = snd_cs4231_new_mixer(pcm);
	if (!mixer)
		goto __nodev;
	if (snd_opl3sa_mixer(oplcard, mixer) < 0)
		goto __nodev;
	if (snd_fm_port[dev] >= 0x340 && snd_fm_port[dev] < 0x400) {
		synth = snd_opl3_new_device(card, snd_fm_port[dev],
					    snd_fm_port[dev] + 2,
					    OPL3_HW_OPL3, 1);
		if (!synth)
			goto __nodev;
	}
	if (snd_midi_port[dev] >= 0x300 && snd_midi_port[dev] < 0x340) {
		rmidi = snd_mpu401_uart_new_device(card, MPU401_HW_OPL3SA,
						   snd_midi_port[dev],
						   oplcard->irqptr->irq);
		if (!rmidi)
			goto __nodev;
	}
	if (synth)
		if (snd_synth_register(synth) < 0)
			goto __nodev;
	if (rmidi) {
		if (snd_rawmidi_register(rmidi, 0) < 0) {
			if (synth)
				snd_synth_unregister(synth);
			synth = NULL;
			goto __nodev;
		}
	}
	if (snd_pcm_register(pcm, 0) < 0) {
		if (synth)
			snd_synth_unregister(synth);
		synth = NULL;
		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;
		snd_pcm_unregister(pcm);
		pcm = NULL;
		goto __nodev;
	}
	sprintf(card->longname, "%s at 0x%x, irq %i, dma %i",
		card->shortname,
		oplcard->port,
		oplcard->irqptr->irq,
		oplcard->dma1ptr->dma);
	if (oplcard->dma2ptr != NULL)
		sprintf(card->longname + strlen(card->longname), "&%i",
			oplcard->dma2ptr->dma);

	if (!snd_card_register(card)) {
		oplcard->pcm = pcm;
		oplcard->mixer = mixer;
		oplcard->rawmidi = rmidi;
		oplcard->synth = synth;
		snd_enable_irq(oplcard->irqptr);
		return 0;
	}
	snd_mixer_unregister(mixer);
	mixer = NULL;
	snd_pcm_unregister(pcm);
	pcm = NULL;

      __nodev:
	if (rmidi)
		snd_rawmidi_free(rmidi);
	if (mixer)
		snd_mixer_free(mixer);
	if (pcm)
		snd_pcm_free(pcm);
	oplcard->card = NULL;
	snd_card_free(card);
	return -ENXIO;
}

int opl3_init(void)
{
	int dev, cards;
	struct snd_opl3sa *oplcard;

#ifdef SNDCFG_ISAPNP
	isapnp_init();
#endif

	for (dev = cards = 0; dev < SND_CARDS && snd_port[dev] > 0; dev++) {
		oplcard = (struct snd_opl3sa *)
				snd_malloc(sizeof(struct snd_opl3sa));
		if (!oplcard)
			continue;
		memset(oplcard, 0, sizeof(struct snd_opl3sa));
		snd_mutex_prepare(oplcard, reg);
		if (snd_opl3sa_probe(dev, oplcard) < 0) {
#ifdef SNDCFG_ISAPNP
            snd_opl3sa_deactivate(oplcard);
#endif			
			snd_free(oplcard, sizeof(struct snd_opl3sa));
			if (snd_port[dev] == SND_AUTO_PORT)
				break;
			snd_printk("Yamaha OPL3-SA soundcard #%i not found at 0x%x or device busy\n", dev + 1, snd_port[dev]);
			continue;
		}
		snd_opl3sa_cards[dev] = oplcard;
		cards++;
	}
	if (!cards) {
		snd_printk("Yamaha OPL3-SA soundcard not found or device busy\n");
		return -ENODEV;
	}
	return 0;
}

void opl3_cleanup(void)
{
	int idx;
	struct snd_opl3sa *oplcard;
	snd_synth_t *synth;
	snd_rawmidi_t *rawmidi;
	snd_kmixer_t *mixer;
	snd_pcm_t *pcm;

	for (idx = 0; idx < SND_CARDS; idx++) {
		oplcard = snd_opl3sa_cards[idx];
		if (oplcard) {
			snd_card_unregister(oplcard->card);
			if (oplcard->synth) {
				synth = oplcard->synth;
				oplcard->synth = NULL;
				snd_synth_unregister(synth);
			}
			if (oplcard->rawmidi) {
				rawmidi = oplcard->rawmidi;
				oplcard->rawmidi = NULL;
				snd_rawmidi_unregister(rawmidi);
			}
			if (oplcard->mixer) {
				mixer = oplcard->mixer;
				oplcard->mixer = NULL;
				snd_mixer_unregister(mixer);
			}
			if (oplcard->pcm) {
				pcm = oplcard->pcm;
				oplcard->pcm = NULL;	/* turn off interrupts */
				snd_pcm_unregister(pcm);
			}
			snd_card_free(oplcard->card);
			oplcard->card = NULL;
			snd_mutex_destroy(oplcard, reg);
#ifdef SNDCFG_ISAPNP
			snd_opl3sa_deactivate(oplcard);
#endif			
			snd_free(oplcard, sizeof(struct snd_opl3sa));
		}
	}
}

int 
snd_opl3_getopt (int argc, char **argv)
{
	int     c;

	while ((c= getopt (argc, argv, SND_OPTIONS"w:" )) != -1)
	{
		switch (c)
		{
		case SND_IOPORT:
			snd_port[0]= strtol (optarg, NULL, 16);
			break;
		case 'w':
			snd_wss_port[0]= strtol (optarg, NULL, 16);
			break;
		case SND_IRQ:
			snd_irq[0]= atoi (optarg);
			break;
		case SND_DMA:
			snd_dma1[0]= atoi (optarg);
			if (optarg= strchr (optarg, ','))
				snd_dma2[0]= atoi (optarg + 1);
			break;
		case SND_NO_PNP:
			snd_isapnp[0]= 0;	/* no pnp on firts card */
			snd_port[1]=0;		/* no second card in scan list */
			break;
		}
	}
	return 0;
}
