/*
 *  Driver for Gravis UltraSound Extreme 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 "driver.h"
#include "gus.h"
#include "es1688.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 */
int snd_gf1_port[SND_CARDS] = {[0 ... (SND_CARDS) - 1] = -1}; /* 0x210,0x220,0x230,0x240,0x250,0x260,0x270 */
int snd_mpu_port[SND_CARDS] = {[0 ... (SND_CARDS) - 1] = -1}; /* 0x300,0x310,0x320 */
int snd_irq[SND_CARDS] = SND_DEFAULT_IRQ;	/* 5,7,9,10 */
int snd_mpu_irq[SND_CARDS] = SND_DEFAULT_IRQ;	/* 5,7,9,10 */
int snd_irq1[SND_CARDS] = SND_DEFAULT_IRQ;	/* 2,3,5,9,11,12,15 */
int snd_dma8[SND_CARDS] = SND_DEFAULT_DMA;	/* 0,1,3 */
int snd_dma8_size[SND_CARDS] = SND_DEFAULT_DMA_SIZE;	/* 8,16,32,64,128 */
int snd_dma1[SND_CARDS] = SND_DEFAULT_DMA;
int snd_dma1_size[SND_CARDS] = SND_DEFAULT_DMA_SIZE;	/* 8,16,32,64,128 */
int snd_joystick_dac[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 29};
				/* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */
#ifdef MODULE_PARM
MODULE_PARM(snd_index, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_index, "Index value for GUS Extreme soundcard.");
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SND_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for GUS Extreme soundcard.");
MODULE_PARM(snd_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_port, "Port # for GUS Extreme driver.");
MODULE_PARM(snd_gf1_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_gf1_port, "GF1 port # for GUS Extreme driver (optional).");
MODULE_PARM(snd_mpu_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_mpu_port, "MPU-401 port # for GUS Extreme driver.");
MODULE_PARM(snd_irq, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_irq, "IRQ # for GUS Extreme driver.");
MODULE_PARM(snd_mpu_irq, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_mpu_irq, "MPU-401 IRQ # for GUS Extreme driver.");
MODULE_PARM(snd_irq1, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_irq1, "GF1 IRQ # for GUS Extreme driver.");
MODULE_PARM(snd_dma8, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma8, "8-bit DMA # for GUS Extreme driver.");
MODULE_PARM(snd_dma1, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma1, "GF1 DMA # for GUS Extreme driver.");
MODULE_PARM(snd_joystick_dac, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for GUS Extreme driver.");
#endif

static struct snd_gusextreme {
	snd_irq_t * irqptr;
	snd_irq_t * mpuirqptr;
	snd_irq_t * irq1ptr;
	snd_dma_t * dma8ptr;
	snd_dma_t * dma1ptr;
	snd_card_t *card;
	snd_gus_card_t *gus;
	snd_pcm_t *pcm;
	snd_pcm_t *pcm1;	/* GF1 PCM */
	snd_kmixer_t *mixer;
	snd_kmixer_t *mixer1;	/* GF1 mixer */
	snd_rawmidi_t *rmidi;
	snd_synth_t *synth;
	unsigned short gus_status_reg;
} *snd_gusextreme_cards[SND_CARDS] = SND_DEFAULT_PTR;

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

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

static snd_pcm_t *snd_gusextreme_detect(int dev,
					snd_card_t * card,
					snd_gus_card_t * gus,
					unsigned short port,
					snd_irq_t * irqptr,
					snd_irq_t * mpu_irqptr,
					snd_dma_t * dma8ptr)
{
	snd_pcm_t *pcm;
	snd_pcm1_t *pcm1;
	es1688_t *codec;
	unsigned long flags;

	if (snd_register_ioport(card, port, 16, "GUS Extreme - ES1688", NULL) < 0)
		return NULL;
	if (snd_gf1_port[dev] < 0) {	/* auto */
		snd_gf1_port[dev] = port < 0x260 ? port + 0x10 : 0x250;
	}
	if (snd_register_ioport(card, snd_gf1_port[dev], 16,
				"GUS Extreme (Adlib/SB compatibility)", NULL) < 0) {
		snd_unregister_ioports(card);
		return NULL;
	}
	if (snd_register_ioport(card, snd_gf1_port[dev] + 0x100, 12,
				"GUS Extreme (synthesizer)", NULL) < 0) {
		snd_unregister_ioports(card);
		return NULL;
	}
	if (snd_register_ioport(card, snd_gf1_port[dev] + 0x506, 1,
				"GUS Extreme (control)", NULL) < 0) {
		snd_unregister_ioports(card);
		return NULL;
	}
	if (mpu_irqptr) {
		if (snd_register_ioport(card, snd_mpu_port[dev], 2,
					"GUS Extreme - ES1688 - MPU-401", NULL) < 0) {
			snd_unregister_ioports(card);
			return NULL;
		}
	}
	pcm = snd_es1688_new_device(card, port, snd_mpu_port[dev], irqptr,
				    mpu_irqptr, dma8ptr, ES1688_HW_1688);
	if (!pcm) {
		snd_unregister_ioports(card);
		return NULL;
	}
	pcm1 = (snd_pcm1_t *) pcm->private_data;
	codec = (es1688_t *) pcm1->private_data;

	/*
	 * This is main stuff - enable access to GF1 chip...
	 * I'm not sure, if this will work for card which have
	 * ES 1688 chip in another place than 0x220.
	 *
	 * ULTRINIT.EXE:
	 * 0x230 = 0,2,3
	 * 0x240 = 2,0,1
	 * 0x250 = 2,0,3
	 * 0x260 = 2,2,1
	 */

	snd_spin_lock(codec, mixer, &flags);
	snd_es1688_mixer_write(codec, 0x40, 0x0b);	/* don't change!!! */
	snd_spin_unlock(codec, mixer, &flags);
	snd_spin_lock(codec, reg, &flags);
	outb(snd_gf1_port[dev] & 0x040 ? 2 : 0, ES1688P(codec, INIT1));
	outb(0, 0x201);
	outb(snd_gf1_port[dev] & 0x020 ? 2 : 0, ES1688P(codec, INIT1));
	outb(0, 0x201);
	outb(snd_gf1_port[dev] & 0x010 ? 3 : 1, ES1688P(codec, INIT1));
	snd_spin_unlock(codec, reg, &flags);

	snd_delay(10);

	snd_gus_set_port(gus, snd_gf1_port[dev]);
	snd_gf1_i_write8(gus, SND_GF1_GB_RESET, 0);	/* reset GF1 */
#ifdef CONFIG_SND_DEBUG_DETECT
	{
		unsigned char d;

		if (((d = snd_gf1_i_look8(gus, SND_GF1_GB_RESET)) & 0x07) != 0) {
			snd_printk("[0x%x] check 1 failed - 0x%x\n", gus->gf1.port, d);
			goto __nodev;
		}
	}
#else
	if ((snd_gf1_i_look8(gus, SND_GF1_GB_RESET) & 0x07) != 0)
		goto __nodev;
#endif
	snd_delay(16);
	snd_gf1_i_write8(gus, SND_GF1_GB_RESET, 1);	/* release reset */
	snd_delay(16);
#ifdef CONFIG_SND_DEBUG_DETECT
	{
		unsigned char d;

		if (((d = snd_gf1_i_look8(gus, SND_GF1_GB_RESET)) & 0x07) != 1) {
			snd_printk("[0x%x] check 2 failed - 0x%x\n", gus->gf1.port, d);
			goto __nodev;
		}
	}
#else
	if ((snd_gf1_i_look8(gus, SND_GF1_GB_RESET) & 0x07) != 1)
		goto __nodev;
#endif

	if (snd_gus_detect_memory(gus) < 0)
		goto __nodev;
	if (snd_gus_check_version(gus) < 0)
		goto __nodev;
	if (gus->card->type != SND_CARD_TYPE_GUS_EXTREME) {
		snd_printdd("GUS Extreme soundcard weren't detected at 0x%x\n", gus->gf1.port);
		goto __nodev;
	}
	return pcm;

      __nodev:
	snd_pcm_free(pcm);
	snd_unregister_ioports(card);
	return NULL;
}

static void snd_gusextreme_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	struct snd_gusextreme *acard = (struct snd_gusextreme *) dev_id;
	unsigned short status;
	int loop, max = 100;

	if (!acard || !acard->gus)
		return;

	do {
		loop = 0;
		if ((status = inb(acard->gus_status_reg))) {
			snd_gus_interrupt(acard->gus, status);
			loop++;
		}
	} while (loop && --max > 0);
}

static void snd_gusextreme_ess_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	struct snd_gusextreme *acard;

	acard = (struct snd_gusextreme *) dev_id;
	if (acard == NULL || acard->pcm == NULL)
		return;
	snd_es1688_interrupt(acard->pcm);
}

static void snd_gusextreme_ess_midi_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	struct snd_gusextreme *acard;

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

static int snd_gusextreme_resources(int dev, struct snd_gusextreme *acard, snd_card_t * card)
{
	static int possible_irqs[] = {5, 9, 10, 7, -1};
	static int possible_dmas[] = {1, 3, 0, -1};
	static int possible_irqs1[] = {5, 11, 12, 9, 7, 15, 3, -1};
	static int possible_dmas1[] = {5, 6, 7, 1, 3, -1};
	int err;

	if ((err = snd_register_interrupt(card,
			"GUS Extreme - ES1688", snd_irq[dev],
			SND_IRQ_TYPE_ISA, snd_gusextreme_ess_interrupt,
			acard, possible_irqs, &acard->irqptr)) < 0)
		return err;
	if ((err = snd_register_dma_channel(card,
			"GUS Extreme - ESx688", snd_dma8[dev],
			SND_DMA_TYPE_ISA, snd_dma8_size[dev],
			possible_dmas, &acard->dma8ptr)) < 0)
		return err;
	acard->mpuirqptr = NULL;
	if (snd_mpu_port[dev] >= 0x300 && snd_mpu_port[dev] <= 0x330 &&
	    (snd_mpu_port[dev] & 0x00f) == 0) {
		snd_register_interrupt(card,
				"GUS Extreme - ES1688 - MPU-401",
				snd_mpu_irq[dev], SND_IRQ_TYPE_ISA,
				snd_gusextreme_ess_midi_interrupt,
				acard, possible_irqs, &acard->mpuirqptr);
	}
	if ((err = snd_register_interrupt(card,
			"GUS Extreme - GF1", snd_irq1[dev],
			SND_IRQ_TYPE_ISA, snd_gusextreme_interrupt,
			acard, possible_irqs1, &acard->irq1ptr)) < 0)
		return err;
	if ((err = snd_register_dma_channel(card,
			"GF1 playback", snd_dma1[dev],
			SND_DMA_TYPE_ISA, snd_dma1_size[dev],
			possible_dmas1, &acard->dma1ptr)) < 0)
		return err;
	return 0;
}

static void snd_gusextreme_init(int dev, snd_gus_card_t * gus)
{
	gus->ess_flag = 1;
	gus->joystick_dac = snd_joystick_dac[dev];
}

static int snd_gusextreme_mixer(snd_kmixer_t * mixer)
{
	if (snd_mixer_element_rename(mixer, SND_MIXER_IN_AUX, 0, SND_MIXER_ETYPE_INPUT, SND_MIXER_IN_SYNTHESIZER, 0) < 0)
		return -1;
	if (snd_mixer_element_rename(mixer, "Aux Volume", 0, SND_MIXER_ETYPE_VOLUME1, "Synth Volume", 0) < 0)
		return -1;
	return 0;
}

static int snd_gusextreme_probe(int dev, struct snd_gusextreme *acard)
{
	static int possible_ports[] = {0x220, 0x240, 0x260, -1};
	int *ports = possible_ports;
	snd_card_t *card;
	snd_gus_card_t *gus = NULL;
	snd_pcm_t *pcm = NULL;
	snd_pcm1_t *pcm_1;
	snd_pcm_t *pcm1 = NULL;
	snd_kmixer_t *mixer = NULL;
	snd_kmixer_t *mixer1 = NULL;
	snd_rawmidi_t *rmidi = NULL;
	snd_synth_t *synth = NULL;
	es1688_t *codec = NULL;
	snd_kmixer_element_t *parent;

	card = snd_card_new(snd_index[dev], snd_id[dev],
			    snd_gusextreme_use_inc, snd_gusextreme_use_dec);
	if (!card)
		return -ENOMEM;
	card->type = SND_CARD_TYPE_GUS_EXTREME;
	if (snd_gusextreme_resources(dev, acard, card) < 0) {
		snd_card_free(card);
		return -EBUSY;
	}
	gus = snd_gus_new_card(card,
			       snd_port[dev],
			       acard->irq1ptr,
			       acard->dma1ptr,
			       NULL,
			       0);
	if (!gus) {
		snd_card_free(card);
		return -ENOMEM;
	}
	if (snd_port[dev] == SND_AUTO_PORT) {
		for (ports = possible_ports; *ports >= 0; ports++) {
			pcm = snd_gusextreme_detect(dev, card, gus,
			                            *ports, acard->irqptr,
			                            acard->mpuirqptr,
			                            acard->dma8ptr);
			if (pcm)
				break;
		}
		if (!pcm) {
			snd_card_free(card);
			return -ENODEV;
		}
	} else {
		pcm = snd_gusextreme_detect(dev, card, gus,
					    snd_port[dev], acard->irqptr,
					    acard->mpuirqptr, acard->dma8ptr);
		if (!pcm) {
			snd_card_free(card);
			return -ENODEV;
		}
	}
	pcm_1 = (snd_pcm1_t *) pcm->private_data;
	codec = (es1688_t *) pcm_1->private_data;
	acard->gus_status_reg = gus->gf1.reg_irqstat;
	snd_gusextreme_init(dev, gus);
	if (snd_gus_init_dma_irq(gus, 1) < 0) {
		snd_pcm_free(pcm);
		snd_card_free(card);
		return -EINVAL;
	}
	mixer = snd_es1688_new_mixer(pcm, 0);
	if (!mixer)
		goto __nodev;
	if (snd_gusextreme_mixer(mixer) < 0)
		goto __nodev;
	if (snd_es1688_init(pcm, 1) < 0)
		goto __nodev;

	parent = snd_mixer_element_find(mixer, SND_MIXER_IN_SYNTHESIZER, 0, SND_MIXER_ETYPE_INPUT);
	if (!parent)
		goto __nodev;
	pcm1 = snd_gf1_pcm_new_device(gus, mixer, parent, 1);
	if (!pcm1)
		goto __nodev;
	mixer1 = snd_gf1_new_mixer(gus);
	if (!mixer1)
		goto __nodev;

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

	if (acard->mpuirqptr) {
		rmidi = snd_mpu401_uart_new_device(card, MPU401_HW_ES1688,
						   codec->mpu_port,
						   codec->mpu_irq);
		if (!rmidi)
			goto __nodev;
		if (snd_rawmidi_register(rmidi, 0) < 0)
			goto __nodev;
	}
#ifdef CONFIG_SND_SEQUENCER
	if (snd_gus_attach_synthesizer(gus) < 0) {
		if (rmidi)
			snd_rawmidi_unregister(rmidi);
		rmidi = NULL;
		goto __nodev;
	}
#endif
	if (synth && snd_synth_register(synth) < 0) {
#ifdef CONFIG_SND_SEQUENCER
		snd_gus_detach_synthesizer(gus);
#endif
		if (rmidi)
			snd_rawmidi_unregister(rmidi);
		rmidi = NULL;
		goto __nodev;
	}
	if (snd_mixer_register(mixer, 0) < 0) {
#ifdef CONFIG_SND_SEQUENCER
		snd_gus_detach_synthesizer(gus);
#endif
		if (synth)
			snd_synth_unregister(synth);
		synth = NULL;
		if (rmidi)
			snd_rawmidi_unregister(rmidi);
		rmidi = NULL;
		goto __nodev;
	}
	if (snd_mixer_register(mixer1, 1) < 0) {
#ifdef CONFIG_SND_SEQUENCER
		snd_gus_detach_synthesizer(gus);
#endif
		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 (snd_pcm_register(pcm, 0) < 0) {
#ifdef CONFIG_SND_SEQUENCER
		snd_gus_detach_synthesizer(gus);
#endif
		if (synth)
			snd_synth_unregister(synth);
		synth = NULL;
		if (rmidi)
			snd_rawmidi_unregister(rmidi);
		rmidi = NULL;
		snd_mixer_unregister(mixer1);
		mixer1 = NULL;
		snd_mixer_unregister(mixer);
		mixer = NULL;
		goto __nodev;
	}
	if (snd_pcm_register(pcm1, 1) < 0) {
#ifdef CONFIG_SND_SEQUENCER
		snd_gus_detach_synthesizer(gus);
#endif
		if (synth)
			snd_synth_unregister(synth);
		synth = NULL;
		if (rmidi)
			snd_rawmidi_unregister(rmidi);
		rmidi = NULL;
		snd_pcm_unregister(pcm);
		pcm = NULL;
		snd_mixer_unregister(mixer1);
		mixer1 = NULL;
		snd_mixer_unregister(mixer);
		mixer = NULL;
		goto __nodev;
	}
	sprintf(card->longname, "Gravis UltraSound Extreme at 0x%x, irq %i&%i, dma %i&%i",
		codec->port,
		acard->irqptr->irq,
		acard->irq1ptr->irq,
		acard->dma8ptr->dma,
		acard->dma1ptr->dma);
	snd_gf1_start(gus);
	if (!snd_card_register(card)) {
		acard->card = card;
		acard->gus = gus;
		acard->pcm = pcm;
		acard->pcm1 = pcm1;
		acard->mixer = mixer;
		acard->mixer1 = mixer1;
		acard->rmidi = rmidi;
		acard->synth = synth;
		return 0;
	}
	snd_gf1_stop(gus);
#ifdef CONFIG_SND_SEQUENCER
	snd_gus_detach_synthesizer(gus);
#endif
	snd_synth_unregister(synth);
	synth = NULL;
	snd_rawmidi_unregister(rmidi);
	rmidi = NULL;
	snd_pcm_unregister(pcm1);
	pcm1 = NULL;
	snd_pcm_unregister(pcm);
	pcm = NULL;
	snd_mixer_unregister(mixer1);
	mixer1 = NULL;
	snd_mixer_unregister(mixer);
	mixer = NULL;

      __nodev:
	snd_gus_init_dma_irq(gus, 0);
	if (synth)
		snd_synth_free(synth);
	if (rmidi)
		snd_rawmidi_free(rmidi);
	if (mixer)
		snd_mixer_free(mixer);
	if (mixer1)
		snd_mixer_free(mixer1);
	if (pcm)
		snd_pcm_free(pcm);
	if (pcm1)
		snd_pcm_free(pcm1);
	snd_card_free(card);
	return -ENXIO;
}


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

#ifndef LINUX_2_1
	register_symtab(NULL);
#endif
	for (dev = cards = 0; dev < SND_CARDS && snd_port[dev] > 0; dev++) {
		acard = (struct snd_gusextreme *)
				snd_malloc(sizeof(struct snd_gusextreme));
		if (!acard)
			continue;
		memset(acard, 0, sizeof(struct snd_gusextreme));
		if (snd_gusextreme_probe(dev, acard) < 0) {
			snd_free(acard, sizeof(struct snd_gusextreme));
			if (snd_port[dev] == SND_AUTO_PORT)
				break;
			snd_printk("GUS Extreme soundcard #%i not found at 0x%x or device busy\n", dev + 1, snd_port[dev]);
			continue;
		}
		snd_gusextreme_cards[dev] = acard;
		cards++;
	}
	if (!cards) {
		snd_printk("GUS Extreme soundcard #%i not found or device busy\n", dev + 1);
		return -ENODEV;
	}
	return 0;
}

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

	for (idx = 0; idx < SND_CARDS; idx++) {
		acard = snd_gusextreme_cards[idx];
		if (acard) {
			snd_card_unregister(acard->card);
			if (acard->gus) {
				snd_gf1_stop(acard->gus);
				snd_gus_init_dma_irq(acard->gus, 0);
			}
#ifdef CONFIG_SND_SEQUENCER
			snd_gus_detach_synthesizer(acard->gus);
#endif
			if (acard->synth)
				snd_synth_unregister(acard->synth);
			if (acard->rmidi)
				snd_rawmidi_unregister(acard->rmidi);
			if (acard->mixer1)
				snd_mixer_unregister(acard->mixer1);
			if (acard->mixer)
				snd_mixer_unregister(acard->mixer);
			if (acard->pcm1) {
				snd_pcm_unregister(acard->pcm1);
			}
			if (acard->pcm) {
				snd_es1688_init(acard->pcm, 0);
				snd_pcm_unregister(acard->pcm);
			}
			snd_card_free(acard->card);
			snd_free(acard, sizeof(struct snd_gusextreme));
		}
	}
}
