/*
 *  Driver for Yamaha OPL3-SA[2,3] soundcards
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#define __SND_OSS_COMPAT__
#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"

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;	/* 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 */
#ifdef CONFIG_ISAPNP
int snd_isapnp[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 1};
#endif
MODULE_PARM(snd_index, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_index, "Index value for OPL3-SA soundcard.");
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SND_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for OPL3-SA soundcard.");
MODULE_PARM(snd_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_port, "Port # for OPL3-SA driver.");
MODULE_PARM(snd_sb_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_sb_port, "SB port # for OPL3-SA driver.");
MODULE_PARM(snd_wss_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_wss_port, "WSS port # for OPL3-SA driver.");
MODULE_PARM(snd_fm_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_fm_port, "FM port # for OPL3-SA driver.");
MODULE_PARM(snd_midi_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_midi_port, "MIDI port # for OPL3-SA driver.");
MODULE_PARM(snd_irq, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_irq, "IRQ # for OPL3-SA driver.");
MODULE_PARM(snd_dma1, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma1, "DMA1 # for OPL3-SA driver.");
MODULE_PARM(snd_dma2, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma2, "DMA2 # for OPL3-SA driver.");
MODULE_PARM(snd_dma1_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma1_size, "DMA1 size in kB for OPL3-SA driver.");
MODULE_PARM(snd_dma2_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma2_size, "DMA2 size in kB for OPL3-SA driver.");
MODULE_PARM(snd_isapnp, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_isapnp, "ISA PnP detection for specified soundcard.");

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_hwdep_t *synth;
	unsigned short pcm_status_reg;
	spinlock_t reg_lock;
	snd_kmixer_element_t *me_vol_mic;
	snd_kmixer_element_t *me_sw_mic;
	snd_kmixer_element_t *me_vol_master;
	snd_kmixer_element_t *me_sw_master;
	snd_kmixer_element_t *me_tone;
#ifdef CONFIG_ISAPNP
	struct isapnp_dev *dev;
#endif
};

static struct snd_opl3sa *snd_opl3sa_cards[SND_CARDS] = SND_DEFAULT_PTR;

#ifdef CONFIG_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 */
	/* 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 */
	/* ??? */
	(ISAPNP_VENDOR('Y','M','H')<<16)|ISAPNP_DEVICE(0x0800),   /* DEVICE */
	(ISAPNP_VENDOR('Y','M','H')<<16)|ISAPNP_FUNCTION(0x0021), /* all */
	/* --- */
	0       /* end */
};
#endif

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

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

static unsigned char snd_opl3sa_read(unsigned short port, unsigned char reg)
{
	unsigned long flags;
	unsigned char result;

	save_flags(flags);
	cli();
#if 0
	outb(0x1d, port);	/* password */
	printk("read [0x%x] = 0x%x\n", port, inb(port));
#endif
	outb(reg, port);	/* register */
	result = inb(port + 1);
	restore_flags(flags);
#if 0
	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)
{
	unsigned long flags;

	save_flags(flags);
	cli();
#if 0
	outb(0x1d, port);	/* password */
#endif
	outb(reg, port);	/* register */
	outb(value, port + 1);
	restore_flags(flags);
}

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);
	if (tmp == 0xff) {
		snd_printd("OPL3-SA [0x%x] detect = 0x%x\n", port, tmp);
		goto __nodev;
	}
	switch (tmp & 0x07) {
	case 0x01:
		oplcard->version = 2;
		break;
	default:
		oplcard->version = 3;
		/* 0x02 - standard */
		/* 0x03 - YM715B */
		/* 0x04 - YM719 - OPL-SA4? */
		/* 0x05 - OPL3-SA3 - Libretto 100 */
		break;
#if 0
	default:
		snd_printd("OPL3-SA [0x%x] detect (0) = 0x%x\n", port, tmp);
		goto __nodev;
#endif
	}
	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;
	}
	/* try if the MIC register is accesible */
	tmp = snd_opl3sa_read(port, 0x09);
	snd_opl3sa_write(port, 0x09, 0x8a);
	if (((tmp1 = snd_opl3sa_read(port, 0x09)) & 0x9f) != 0x8a) {
		snd_printd("OPL3-SA [0x%x] detect (2) = 0x%x (0x%x)\n", port, tmp, tmp1);
		goto __nodev;
	}
	snd_opl3sa_write(port, 0x09, 0x9f);
	/* initialization */
	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(int irq, void *dev_id, struct pt_regs *regs)
{
	unsigned short status;
	unsigned long flags;
	struct snd_opl3sa *oplcard = (struct snd_opl3sa *) dev_id;

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

	spin_lock_irqsave(&oplcard->reg_lock, flags);
	outb(0x04, oplcard->port);	/* register - Interrupt IRQ-A status */
	status = inb(oplcard->port + 1);
	spin_unlock_irqrestore(&oplcard->reg_lock, flags);

	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 */
		status = inb(oplcard->pcm_status_reg);
		if (status & 0x01)
			snd_cs4231_interrupt(oplcard->pcm, status);
	}

	if (status & 0x40) {
		/* reading from Master Lch register at 0x07 clears this bit */
		snd_opl3sa_read(oplcard->port, 0x08);
		snd_opl3sa_read(oplcard->port, 0x07);
		snd_mixer_element_value_change_all(oplcard->mixer, oplcard->me_vol_master, 1);
		snd_mixer_element_value_change_all(oplcard->mixer, oplcard->me_sw_master, 1);
	}
}

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_master_volume(void *private_data,
				    int w_flag, int *voices)
{
	struct snd_opl3sa *oplcard = (struct snd_opl3sa *)private_data;
	unsigned long flags;
	unsigned char lreg, rreg, oleft, oright;
	int change = 0;
	
	spin_lock_irqsave(&oplcard->reg_lock, flags);
	oleft = 15 - ((lreg = snd_opl3sa_read(oplcard->port, 0x07)) & 15);
	oright = 15 - ((rreg = snd_opl3sa_read(oplcard->port, 0x08)) & 15);
	if (!w_flag) {
		voices[0] = oleft;
		voices[1] = oright;
	} else {
		change = oleft != (voices[0] & 15) || oright != (voices[1] & 15);
		lreg &= ~15;
		lreg |= 15 - (voices[0] & 15);
		rreg &= ~15;
		rreg |= 15 - (voices[1] & 15);
		snd_opl3sa_write(oplcard->port, 0x07, lreg);
		snd_opl3sa_write(oplcard->port, 0x08, rreg);
	}
	spin_unlock_irqrestore(&oplcard->reg_lock, flags);
	return change;
	
}

static int snd_opl3sa_master_switch(void *private_data,
				    int w_flag, unsigned int *bitmap)
{
	struct snd_opl3sa *oplcard = (struct snd_opl3sa *)private_data;
	unsigned long flags;
	unsigned char lreg, rreg, oleft, oright;
	int change = 0;
	
	spin_lock_irqsave(&oplcard->reg_lock, flags);
	oleft = (lreg = snd_opl3sa_read(oplcard->port, 0x07)) & 0x80 ? 0 : 1;
	oright = (rreg = snd_opl3sa_read(oplcard->port, 0x08)) & 0x80 ? 0 : 1;
	if (!w_flag) {
		snd_mixer_set_bit(bitmap, 0, oleft);
		snd_mixer_set_bit(bitmap, 1, oright);
	} else {
		change = oleft != snd_mixer_get_bit(bitmap, 0) ||
		         oright != snd_mixer_get_bit(bitmap, 1);
		lreg &= 0x7f;
		if (!snd_mixer_get_bit(bitmap, 0))
			lreg |= 0x80;
		rreg &= 0x7f;
		if (!snd_mixer_get_bit(bitmap, 1))
			rreg |= 0x80;
		snd_opl3sa_write(oplcard->port, 0x07, lreg);
		snd_opl3sa_write(oplcard->port, 0x08, rreg);
	}
	spin_unlock_irqrestore(&oplcard->reg_lock, flags);
	return change;
	
}

static int snd_opl3sa_mic_volume(void *private_data,
				 int w_flag, int *voices)
{
	struct snd_opl3sa *oplcard = (struct snd_opl3sa *)private_data;
	unsigned long flags;
	unsigned char reg, oval;
	int change = 0;
	
	spin_lock_irqsave(&oplcard->reg_lock, flags);
	oval = 31 - ((reg = snd_opl3sa_read(oplcard->port, 0x09)) & 31);
	if (!w_flag) {
		voices[0] = oval;
	} else {
		change = oval != (voices[0] & 31);
		reg &= ~31;
		reg |= 31 - (voices[0] & 31);
		snd_opl3sa_write(oplcard->port, 0x09, reg);
	}
	spin_unlock_irqrestore(&oplcard->reg_lock, flags);
	return change;
	
}

static int snd_opl3sa_mic_switch(void *private_data,
				 int w_flag, int *value)
{
	struct snd_opl3sa *oplcard = (struct snd_opl3sa *)private_data;
	unsigned long flags;
	unsigned char reg, oval;
	int change = 0;
	
	spin_lock_irqsave(&oplcard->reg_lock, flags);
	oval = (reg = snd_opl3sa_read(oplcard->port, 0x09)) & 0x80 ? 0 : 1;
	if (!w_flag) {
		*value = oval;
	} else {
		change = oval != *value;
		reg &= 0x7f;
		if (!*value)
			reg |= 0x80;
		snd_opl3sa_write(oplcard->port, 0x09, reg);
	}
	spin_unlock_irqrestore(&oplcard->reg_lock, flags);
	return change;
	
}

static int snd_opl3sa_tone_control(void *private_data,
				   int w_flag,
				   struct snd_mixer_element_tone_control1 *tc1)
{
	struct snd_opl3sa *oplcard = (struct snd_opl3sa *)private_data;
	unsigned long flags;
	unsigned char obass, otreble;
	int change = 0;
	
	spin_lock_irqsave(&oplcard->reg_lock, flags);
	obass = snd_opl3sa_read(oplcard->port, 0x15) & 7;
	otreble = snd_opl3sa_read(oplcard->port, 0x16) & 7;
	if (w_flag) {
		if (tc1->tc & SND_MIXER_TC1_BASS) {
			change |= obass != tc1->bass;
			obass = ((tc1->bass & 7) << 4) | (tc1->bass & 7);
			snd_opl3sa_write(oplcard->port, 0x15, obass);
		}
		if (tc1->tc & SND_MIXER_TC1_TREBLE) {
			change |= otreble != tc1->treble;
			otreble = ((tc1->treble & 7) << 4) | (tc1->treble & 7);
			snd_opl3sa_write(oplcard->port, 0x16, otreble);
		}
	} else {
		tc1->tc = SND_MIXER_TC1_BASS | SND_MIXER_TC1_TREBLE;
		tc1->bass = obass;
		tc1->treble = otreble;
	}
	spin_unlock_irqrestore(&oplcard->reg_lock, flags);
	return change;
}

static int snd_opl3sa_3d_control(void *private_data,
				 int w_flag,
				 struct snd_mixer_element_3d_effect1 *eff)
{
	struct snd_opl3sa *oplcard = (struct snd_opl3sa *)private_data;
	unsigned long flags;
	unsigned char owide;
	int change = 0;
	
	spin_lock_irqsave(&oplcard->reg_lock, flags);
	owide = snd_opl3sa_read(oplcard->port, 0x14) & 7;
	if (w_flag) {
		if (eff->effect & SND_MIXER_EFF1_WIDE) {
			change |= owide != eff->wide;
			owide = ((eff->wide & 7) << 4) | (eff->wide & 7);
			snd_opl3sa_write(oplcard->port, 0x14, owide);
		}
	} else {
		eff->effect = SND_MIXER_EFF1_WIDE;
		eff->wide = owide;
	}
	spin_unlock_irqrestore(&oplcard->reg_lock, flags);
	return change;
}

static int snd_opl3sa_group_master(void *private_data,
				   snd_kmixer_file_t * file,
				   int w_flag,
				   snd_mixer_group_t * ugroup)
{
	struct snd_opl3sa *oplcard = (struct snd_opl3sa *)private_data;
	int voices[2];
	unsigned int bitmap;
	int change = 0;

	if (!w_flag) {
		ugroup->caps = SND_MIXER_GRPCAP_VOLUME | SND_MIXER_GRPCAP_MUTE;
		ugroup->channels = SND_MIXER_CHN_MASK_STEREO;
		snd_opl3sa_master_volume(private_data, 0, voices);
		ugroup->volume.names.front_left = voices[0];
		ugroup->volume.names.front_right = voices[1];
		ugroup->min = 0;
		ugroup->max = 15;
		snd_opl3sa_master_switch(private_data, 0, &bitmap);
		ugroup->mute = 0;
		if (!snd_mixer_get_bit(&bitmap, 0))
			ugroup->mute |= SND_MIXER_CHN_MASK_FRONT_LEFT;
		if (!snd_mixer_get_bit(&bitmap, 1))
			ugroup->mute |= SND_MIXER_CHN_MASK_FRONT_RIGHT;
	} else {
		voices[0] = ugroup->volume.names.front_left & 15;
		voices[1] = ugroup->volume.names.front_right & 15;
		if (snd_opl3sa_master_volume(private_data, 1, voices) > 0) {
			snd_mixer_element_value_change(file, oplcard->me_vol_master, 0);
			change = 1;
		}
		bitmap = 0;
		if (!(ugroup->mute & SND_MIXER_CHN_MASK_FRONT_LEFT))
			snd_mixer_set_bit(&bitmap, 0, 1);
		if (!(ugroup->mute & SND_MIXER_CHN_MASK_FRONT_RIGHT))
			snd_mixer_set_bit(&bitmap, 1, 1);
		if (snd_opl3sa_master_switch(private_data, 1, &bitmap) > 0) {
			snd_mixer_element_value_change(file, oplcard->me_sw_master, 0);
			change = 1;
		}
	}
	return change;
}

static int snd_opl3sa_group_mic(void *private_data,
				snd_kmixer_file_t * file,
				int w_flag,
				snd_mixer_group_t * ugroup)
{
	struct snd_opl3sa *oplcard = (struct snd_opl3sa *)private_data;
	cs4231_t *codec = (cs4231_t *)(oplcard->pcm->private_data);
	int voice, value, change = 0;
	snd_kmixer_element_t *elements[2];

	if (!w_flag) {
		ugroup->caps = SND_MIXER_GRPCAP_VOLUME | 
			       SND_MIXER_GRPCAP_JOINTLY_VOLUME |
			       SND_MIXER_GRPCAP_MUTE |
			       SND_MIXER_GRPCAP_JOINTLY_MUTE |
			       SND_MIXER_GRPCAP_CAPTURE |
			       SND_MIXER_GRPCAP_EXCL_CAPTURE;
		ugroup->channels = SND_MIXER_CHN_MASK_STEREO;
		snd_opl3sa_mic_volume(private_data, 0, &voice);
		ugroup->volume.names.front_left = voice;
		ugroup->volume.names.front_right = voice;
		ugroup->min = 0;
		ugroup->max = 31;
		snd_opl3sa_mic_switch(private_data, 0, &value);
		ugroup->mute = 0;
		if (!value)
			ugroup->mute |= SND_MIXER_CHN_MASK_STEREO;
		ugroup->capture_group = 1;
		snd_cs4231_mixer_mux(codec, 0, elements);
		ugroup->capture = 0;
		if (elements[0] == codec->me_mux_mic)
			ugroup->capture |= SND_MIXER_CHN_MASK_FRONT_LEFT;
		if (elements[1] == codec->me_mux_mic)
			ugroup->capture |= SND_MIXER_CHN_MASK_FRONT_RIGHT;
	} else {
		voice = ugroup->volume.names.front_left & 31;
		if (snd_opl3sa_mic_volume(private_data, 1, &voice) > 0) {
			snd_mixer_element_value_change(file, oplcard->me_vol_mic, 0);
			change = 1;
		}
		value = 0;
		if (!(ugroup->mute & SND_MIXER_CHN_MASK_STEREO))
			value = 1;
		if (snd_opl3sa_mic_switch(private_data, 1, &value) > 0) {
			snd_mixer_element_value_change(file, oplcard->me_sw_mic, 0);
			change = 1;
		}
		snd_cs4231_mixer_mux(codec, 0, elements);
		if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_LEFT)
			elements[0] = codec->me_mux_mic;
		if (ugroup->capture & SND_MIXER_CHN_MASK_FRONT_RIGHT)
			elements[1] = codec->me_mux_mic;
		if (snd_cs4231_mixer_mux(codec, 1, elements) > 0) {
			snd_mixer_element_value_change(file, codec->me_mux_mic, 0);
			change = 1;
		}
	}
	return change;
}

static int snd_opl3sa_group_bass(void *private_data,
				 snd_kmixer_file_t * file,
				 int w_flag,
				 snd_mixer_group_t * ugroup)
{
	struct snd_opl3sa *oplcard = (struct snd_opl3sa *)private_data;
	struct snd_mixer_element_tone_control1 tc;
	int change = 0;

	if (!w_flag) {
		ugroup->caps = SND_MIXER_GRPCAP_VOLUME;
		ugroup->channels = SND_MIXER_CHN_MASK_MONO;
		snd_opl3sa_tone_control(oplcard, 0, &tc);
		ugroup->volume.names.front_left = tc.bass;
		ugroup->min = 0;
		ugroup->max = 7;
	} else {
		tc.tc = SND_MIXER_TC1_BASS;
		tc.bass = ugroup->volume.names.front_left & 7;
		if (snd_opl3sa_tone_control(oplcard, 1, &tc) > 0) {
			snd_mixer_element_value_change(file, oplcard->me_tone, 0);
			change = 1;
		}
	}
	return change;
}

static int snd_opl3sa_group_treble(void *private_data,
				   snd_kmixer_file_t * file,
				   int w_flag,
				   snd_mixer_group_t * ugroup)
{
	struct snd_opl3sa *oplcard = (struct snd_opl3sa *)private_data;
	struct snd_mixer_element_tone_control1 tc;
	int change = 0;

	if (!w_flag) {
		ugroup->caps = SND_MIXER_GRPCAP_VOLUME;
		ugroup->channels = SND_MIXER_CHN_MASK_MONO;
		snd_opl3sa_tone_control(oplcard, 0, &tc);
		ugroup->volume.names.front_left = tc.treble;
		ugroup->min = 0;
		ugroup->max = 7;
	} else {
		tc.tc = SND_MIXER_TC1_TREBLE;
		tc.treble = ugroup->volume.names.front_left & 7;
		if (snd_opl3sa_tone_control(oplcard, 1, &tc) > 0) {
			snd_mixer_element_value_change(file, oplcard->me_tone, 0);
			change = 1;
		}
	}
	return change;
}

static int snd_opl3sa_mixer(struct snd_opl3sa *oplcard, cs4231_t *codec, snd_kmixer_t * mixer)
{
	snd_kmixer_group_t *group, *ogroup, *group2;
	snd_kmixer_element_t *element;
	struct snd_mixer_element_tone_control1_info tinfo;
	struct snd_mixer_element_3d_effect1_info einfo;
	static struct snd_mixer_element_volume1_range mic_range[2] = {
		{0, 31, -3450, 1200}
	};
	static struct snd_mixer_element_volume1_range master_range[2] = {
		{0, 15, -3000, 0},
		{0, 15, -3000, 0}
	};

	/* reassign AUX1 to CD */
	if (snd_mixer_group_rename(mixer,
				SND_MIXER_IN_AUX, 0,
				SND_MIXER_IN_CD, 0,
				SND_MIXER_OSS_CD) < 0)
		goto __error;
	if (snd_mixer_element_rename(mixer,
				SND_MIXER_IN_AUX, 0, SND_MIXER_ETYPE_INPUT,
				SND_MIXER_IN_CD, 0) < 0)
		goto __error;
	if (snd_mixer_element_rename(mixer,
				"Aux Input Volume", 0, SND_MIXER_ETYPE_VOLUME1,
				"CD Input Volume", 0) < 0)
		goto __error;
	if (snd_mixer_element_rename(mixer,
				"Aux Input Switch", 0, SND_MIXER_ETYPE_SWITCH1,
				"CD Input Switch", 0) < 0)
		goto __error;
	/* reassign AUX2 to FM */
	if (snd_mixer_group_rename(mixer,
				SND_MIXER_IN_AUX, 1,
				SND_MIXER_IN_FM, 0,
				SND_MIXER_OSS_SYNTH) < 0)
		goto __error;
	if (snd_mixer_element_rename(mixer,
				SND_MIXER_IN_AUX, 1, SND_MIXER_ETYPE_INPUT,
				SND_MIXER_IN_FM, 0) < 0)
		goto __error;
	if (snd_mixer_element_rename(mixer,
				"Aux Input Volume", 1, SND_MIXER_ETYPE_VOLUME1,
				"FM Input Volume", 0) < 0)
		goto __error;
	if (snd_mixer_element_rename(mixer,
				"Aux Input Switch", 1, SND_MIXER_ETYPE_SWITCH1,
				"FM Input Switch", 0) < 0)
		goto __error;
	/* reassign MIC Volume to MIC Gain */
	if (snd_mixer_element_rename(mixer,
				     "MIC Volume", 0, SND_MIXER_ETYPE_VOLUME1,
				     "MIC Gain Volume", 0) < 0)
		goto __error;
	if ((ogroup = snd_mixer_group_find(mixer, SND_MIXER_IN_MIC, 0)) == NULL)
		goto __error;
	ogroup->control = snd_opl3sa_group_mic;
	ogroup->private_data = oplcard;
	if ((oplcard->me_vol_mic = snd_mixer_lib_volume1(mixer, "MIC Volume", 0, 1, mic_range, snd_opl3sa_mic_volume, oplcard)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, ogroup, oplcard->me_vol_mic) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_vol_mic, oplcard->me_vol_mic) < 0)
		goto __error;
	if ((oplcard->me_sw_mic = snd_mixer_lib_sw2(mixer, "MIC Switch", 0, snd_opl3sa_mic_switch, oplcard)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, ogroup, oplcard->me_sw_mic) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, oplcard->me_vol_mic, oplcard->me_sw_mic) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, oplcard->me_sw_mic, codec->me_accu) < 0)
		goto __error;
	/* master volume */
	if (snd_mixer_element_route_remove(mixer, codec->me_accu, codec->me_out_master) < 0)
		goto __error;
	if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_OUT_MASTER, 0, SND_MIXER_OSS_VOLUME, snd_opl3sa_group_master, oplcard)) == NULL)
		goto __error;
	if ((oplcard->me_vol_master = snd_mixer_lib_volume1(mixer, "Master Volume", 0, 2, master_range, snd_opl3sa_master_volume, oplcard)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, oplcard->me_vol_master) < 0)
		goto __error;
	if (snd_mixer_element_route_add(mixer, codec->me_accu, oplcard->me_vol_master) < 0)
		goto __error;
	if ((oplcard->me_sw_master = snd_mixer_lib_sw1(mixer, "Master Switch", 0, 2, snd_opl3sa_master_switch, oplcard)) == NULL)
		goto __error;
	if (snd_mixer_group_element_add(mixer, group, oplcard->me_sw_master) < 0)
		goto __error;
	if (oplcard->version > 2) {
		if ((group = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_GRP_BASS, 0, SND_MIXER_OSS_BASS, snd_opl3sa_group_bass, oplcard)) == NULL)
			goto __error;
		if ((group2 = snd_mixer_lib_group_ctrl(mixer, SND_MIXER_GRP_TREBLE, 0, SND_MIXER_OSS_TREBLE, snd_opl3sa_group_treble, oplcard)) == NULL)
			goto __error;
		memset(&tinfo, 0, sizeof(tinfo));
		tinfo.tc = SND_MIXER_TC1_BASS | SND_MIXER_TC1_TREBLE;
		tinfo.max_bass = tinfo.max_treble = 7;
		tinfo.max_bass_dB = tinfo.max_treble_dB = 1050;
		if ((oplcard->me_tone = snd_mixer_lib_tone_control1(mixer, SND_MIXER_ELEMENT_TONE_CONTROL, 0, &tinfo, snd_opl3sa_tone_control, oplcard)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, oplcard->me_tone) < 0)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group2, oplcard->me_tone) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, oplcard->me_sw_mic, oplcard->me_tone) < 0)
			goto __error;
		if ((group = snd_mixer_lib_group(mixer, SND_MIXER_GRP_EFFECT_3D, 0)) == NULL)
			goto __error;
		memset(&einfo, 0, sizeof(einfo));
		einfo.effect = SND_MIXER_EFF1_WIDE;
		einfo.max_wide = 7;
		if ((element = snd_mixer_lib_3d_effect1(mixer, SND_MIXER_GRP_EFFECT_3D, 0, &einfo, snd_opl3sa_3d_control, oplcard)) == NULL)
			goto __error;
		if (snd_mixer_group_element_add(mixer, group, element) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, oplcard->me_tone, element) < 0)
			goto __error;
		if (snd_mixer_element_route_add(mixer, element, codec->me_out_master) < 0)
			goto __error;		
	} else {
		if (snd_mixer_element_route_add(mixer, oplcard->me_sw_master, codec->me_out_master) < 0)
			goto __error;
	}
	return 0;

      __error:
      	oplcard->me_vol_master = NULL;
      	oplcard->me_sw_master = NULL;
      	return -ENOMEM;
}

#ifdef CONFIG_ISAPNP
static int snd_opl3sa_isapnp(int dev, struct snd_opl3sa *oplcard)
{
        static int idx = 0;
	struct isapnp_card *card = NULL;
        int step = 2;
        unsigned int tmp;
	struct isapnp_dev *pdev;

      __again:
      	oplcard->dev = NULL;
	while ((tmp = snd_opl3sa_pnpids[idx]) != 0) {
		card = isapnp_find_card(tmp >> 16, tmp & 0xffff, card);
		if (card)
			break;
		idx += step;
	}
	if (!card) {
		snd_printdd("isapnp failed for OPL3-SA\n");
		return -ENODEV;
	}
	tmp = snd_opl3sa_pnpids[idx+1];
	oplcard->dev = pdev = isapnp_find_dev(card, tmp >> 16, tmp & 0xffff, NULL);
	if (!pdev) {
                snd_printdd("isapnp failed for OPL3-SA - %i\n", idx);
                goto __again;           /* maybe we have another config */
        }
	/* PnP initialization */
	if (pdev->prepare(pdev)<0)
		return -EAGAIN;
	if (snd_sb_port[dev] != SND_AUTO_PORT)
		isapnp_resource_change(&pdev->resource[0], snd_sb_port[dev], 16);
	if (snd_wss_port[dev] != SND_AUTO_PORT)
		isapnp_resource_change(&pdev->resource[1], snd_wss_port[dev], 8);
	if (snd_fm_port[dev] != SND_AUTO_PORT)
		isapnp_resource_change(&pdev->resource[2], snd_fm_port[dev], 4);
	if (snd_midi_port[dev] != SND_AUTO_PORT)
		isapnp_resource_change(&pdev->resource[3], snd_midi_port[dev], 2);
	if (snd_port[dev] != SND_AUTO_PORT)
		isapnp_resource_change(&pdev->resource[4], snd_port[dev], 2);
	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_irq[dev] != SND_AUTO_IRQ)
		isapnp_resource_change(&pdev->irq_resource[0], snd_irq[dev], 1);
	if (pdev->activate(pdev)<0) {
		snd_printk("OPL3-SA isapnp configure failure (out of resources?)\n");
		return -EBUSY;
	}
	snd_sb_port[dev] = pdev->resource[0].start;
	snd_wss_port[dev] = pdev->resource[1].start;
	snd_fm_port[dev] = pdev->resource[2].start;
	snd_midi_port[dev] = pdev->resource[3].start;
	snd_port[dev] = pdev->resource[4].start;
	snd_dma1[dev] = pdev->dma_resource[0].start;
	snd_dma2[dev] = pdev->dma_resource[1].start;
	snd_irq[dev] = pdev->irq_resource[0].start;
	snd_printdd("isapnp OPL3-SA: sb port=0x%x, wss port=0x%x, fm port=0x%x, midi port=0x%x\n",
		snd_sb_port[dev], snd_wss_port[dev], snd_fm_port[dev], snd_midi_port[dev]);
	snd_printdd("isapnp OPL3-SA: control port=0x%x, dma1=%i, dma2=%i, irq=%i\n",
		snd_port[dev], snd_dma1[dev], snd_dma2[dev], snd_irq[dev]);
	return 0;
}

static void snd_opl3sa_deactivate(struct snd_opl3sa *oplcard)
{
	if (oplcard->dev)
		oplcard->dev->deactivate(oplcard->dev);
}
#endif /* CONFIG_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_hwdep_t *synth = NULL;

#ifdef CONFIG_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 CONFIG_ISAPNP
	}
#endif
	card = snd_card_new(snd_index[dev], snd_id[dev],
			    snd_opl3sa_use_inc, snd_opl3sa_use_dec);
	if (!card)
		return -ENOMEM;
	card->type = SND_CARD_TYPE_OPL3_SA;
	strcpy(card->abbreviation, "OPL3SA");
	strcpy(card->shortname, "Yamaha OPL3-SA");
#ifdef CONFIG_ISAPNP
	if (snd_isapnp[dev] && 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, 0);
	if (!mixer)
		goto __nodev;
	if (snd_opl3sa_mixer(oplcard, (cs4231_t *)(pcm->private_data), 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_hwdep_register(synth, 0) < 0)
			goto __nodev;
	if (rmidi) {
		if (snd_rawmidi_register(rmidi, 0) < 0) {
			if (synth)
				snd_hwdep_unregister(synth);
			synth = NULL;
			goto __nodev;
		}
	}
	if (snd_pcm_register(pcm, 0) < 0) {
		if (synth)
			snd_hwdep_unregister(synth);
		synth = NULL;
		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;
		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)
		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;
		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 init_module(void)
{
	int dev, cards;
	struct snd_opl3sa *oplcard;

	for (dev = cards = 0; dev < SND_CARDS && snd_port[dev] > 0; dev++) {
		oplcard = (struct snd_opl3sa *)
				snd_kcalloc(sizeof(struct snd_opl3sa), GFP_KERNEL);
		if (!oplcard)
			continue;
		oplcard->reg_lock = SPIN_LOCK_UNLOCKED;
		if (snd_opl3sa_probe(dev, oplcard) < 0) {
#ifdef CONFIG_ISAPNP
			snd_opl3sa_deactivate(oplcard);
#endif
			snd_kfree(oplcard);
			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 cleanup_module(void)
{
	int idx;
	struct snd_opl3sa *oplcard;
	snd_hwdep_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_hwdep_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;
#ifdef CONFIG_ISAPNP
			snd_opl3sa_deactivate(oplcard);
#endif
			snd_kfree(oplcard);
		}
	}
}
