/*
 *  Driver for Gravis UltraSound Classic soundcard
 *  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/gus.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;	/* 0x220,0x230,0x240,0x250,0x260 */
int snd_irq[SND_CARDS] = SND_DEFAULT_IRQ;	/* 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 */
int snd_joystick_dac[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 29};
				/* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */
int snd_voices[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 26};
int snd_pcm_voices[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 2};
MODULE_PARM(snd_index, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_index, "Index value for GUS Classic soundcard.");
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SND_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for GUS Classic soundcard.");
MODULE_PARM(snd_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_port, "Port # for GUS Classic driver.");
MODULE_PARM(snd_irq, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_irq, "IRQ # for GUS Classic driver.");
MODULE_PARM(snd_dma1, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma1, "DMA1 # for GUS Classic driver.");
MODULE_PARM(snd_dma2, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma2, "DMA2 # for GUS Classic driver.");
MODULE_PARM(snd_dma1_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma1_size, "DMA1 size in kB for GUS Classic driver.");
MODULE_PARM(snd_dma2_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma2_size, "DMA2 size in kB for GUS Classic 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 Classic driver.");
MODULE_PARM(snd_voices, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_voices, "GF1 voices for GUS Classic driver.");
MODULE_PARM(snd_pcm_voices, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_pcm_voices, "Reserved PCM voices for GUS Classic driver.");

struct snd_gusclassic {
	snd_irq_t * irqptr;
	snd_dma_t * dma1ptr;
	snd_dma_t * dma2ptr;
	snd_card_t *card;
	snd_gus_card_t *gus;
	snd_pcm_t *pcm;
	snd_kmixer_t *mixer;
	snd_rawmidi_t *midi_uart;
	unsigned short gus_status_reg;
};

static struct snd_gusclassic *snd_gusclassic_cards[SND_CARDS] = SND_DEFAULT_PTR;

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

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

static int snd_gusclassic_detect(snd_gus_card_t * gus, unsigned short port)
{
	if (snd_register_ioport(gus->card, port, 16,
				"GUS Classic (Adlib/SB compatibility)", NULL) < 0)
		return -EBUSY;
	if (snd_register_ioport(gus->card, port + 0x100, 12,
				"GUS Classic (synthesizer)", NULL) < 0) {
		snd_unregister_ioports(gus->card);
		return -EBUSY;
	}
	if (snd_register_ioport(gus->card, port + 0x506, 1,
				"GUS Classic (control)", NULL) < 0) {
		snd_unregister_ioports(gus->card);
		return -EBUSY;
	}
	snd_gus_set_port(gus, port);
	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_CLASSIC &&
	    gus->card->type != SND_CARD_TYPE_GUS_ACE) {
		snd_printdd("GUS Classic soundcard weren't detected at 0x%x\n", gus->gf1.port);
		goto __nodev;
	}
	return 0;

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

static void snd_gusclassic_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	struct snd_gusclassic *guscard = (struct snd_gusclassic *) dev_id;
	unsigned short status;
	int loop, max = 100;

	if (!guscard || !guscard->gus || !guscard->pcm)
		return;

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

static int snd_gusclassic_resources(int dev, struct snd_gusclassic *guscard,
				    snd_card_t * card)
{
	static int possible_irqs[] = {5, 11, 12, 9, 7, 15, 3, 4, -1};
	static int possible_dmas[] = {5, 6, 7, 1, 3, -1};
	int err;

	if ((err = snd_register_interrupt(card, "GUS Classic",
			snd_irq[dev], SND_IRQ_TYPE_ISA,
			snd_gusclassic_interrupt, guscard,
			possible_irqs, &guscard->irqptr)) < 0)
		return err;
	if ((err = snd_register_dma_channel(card, "GF1 playback",
			snd_dma1[dev], SND_DMA_TYPE_ISA,
			snd_dma1_size[dev], possible_dmas,
			&guscard->dma1ptr)) < 0)
		return err;
	guscard->dma1ptr->multi = 1;
	guscard->dma1ptr->multi_match[0] = "GF1 PCM - playback";
	if (snd_dma2[dev] >= 0 && snd_dma2[dev] <= 7) {
		if ((err = snd_register_dma_channel(card,
				"GF1 record", snd_dma2[dev],
				SND_DMA_TYPE_ISA, snd_dma2_size[dev],
				possible_dmas, &guscard->dma2ptr)) < 0) {
			return err;
		}
	} else {
		guscard->dma2ptr = NULL;
	}
	return 0;
}

static void snd_gusclassic_init(int dev, snd_gus_card_t * gus)
{
	gus->equal_irq = 0;
	gus->codec_flag = 0;
	gus->max_flag = 0;
	gus->joystick_dac = snd_joystick_dac[dev];
}

static int snd_gusclassic_probe(int dev, struct snd_gusclassic *guscard)
{
	static int possible_ports[] =
		{0x220, 0x230, 0x240, 0x250, 0x260, -1};
	int *ports = possible_ports;
	snd_card_t *card;
	snd_gus_card_t *gus = NULL;
	snd_pcm_t *pcm = NULL;
	snd_kmixer_t *mixer = NULL;
	snd_rawmidi_t *midi_uart = NULL;
	snd_kmixer_element_t *element;

	card = snd_card_new(snd_index[dev], snd_id[dev],
			    snd_gusclassic_use_inc, snd_gusclassic_use_dec);
	if (!card)
		return -ENOMEM;
	if (snd_gusclassic_resources(dev, guscard, card) < 0) {
		snd_card_free(card);
		return -EBUSY;
	}
	if (snd_pcm_voices[dev] < 2)
		snd_pcm_voices[dev] = 2;
	gus = snd_gus_new_card(card,
			       snd_port[dev],
			       guscard->irqptr,
			       guscard->dma1ptr,
			       guscard->dma2ptr,
			       0, snd_voices[dev], snd_pcm_voices[dev], 0);
	if (!gus) {
		snd_card_free(card);
		return -ENOMEM;
	}
	if (snd_port[dev] == SND_AUTO_PORT) {
		for (ports = possible_ports; *ports >= 0; ports++) {
			if (!snd_gusclassic_detect(gus, *ports))
				break;
		}
		if (*ports < 0) {
			snd_card_free(card);
			return -ENODEV;
		}
	} else {
		if (snd_gusclassic_detect(gus, snd_port[dev])) {
			snd_card_free(card);
			return -ENODEV;
		}
	}
	guscard->gus_status_reg = gus->gf1.reg_irqstat;
	snd_gusclassic_init(dev, gus);
	if (snd_gus_init_dma_irq(gus, 1) < 0) {
		snd_card_free(card);
		return -EINVAL;
	}
	mixer = snd_gf1_new_mixer(gus);
	if (!mixer)
		goto __nodev;
	element = snd_mixer_element_find(mixer, SND_MIXER_IN_SYNTHESIZER, 0, SND_MIXER_ETYPE_INPUT);
	if (!element) {
		element = snd_mixer_element_find(mixer, SND_MIXER_ELEMENT_OUTPUT_ACCU, 0, SND_MIXER_ETYPE_ACCU1);
		if (!element)
			goto __nodev;
	}
	pcm = snd_gf1_pcm_new_device(gus, mixer, element, 0);
	if (!pcm)
		goto __nodev;
	if (gus->card->type != SND_CARD_TYPE_GUS_ACE) {
		midi_uart = snd_gf1_rawmidi_new_device(gus);
		if (!midi_uart)
			goto __nodev;
		if (snd_rawmidi_register(midi_uart, 0) < 0)
			goto __nodev;
	}
#ifdef CONFIG_SND_SEQUENCER
	if (snd_gus_attach_synthesizer(gus) < 0) {
		if (midi_uart)
			snd_rawmidi_unregister(midi_uart);
		midi_uart = NULL;
		goto __nodev;
	}
#endif
	if (snd_mixer_register(mixer, 0) < 0) {
		if (midi_uart)
			snd_rawmidi_unregister(midi_uart);
		midi_uart = NULL;
#ifdef CONFIG_SND_SEQUENCER
		snd_gus_detach_synthesizer(gus);
#endif
		goto __nodev;
	}
	if (snd_pcm_register(pcm, 0) < 0) {
		if (midi_uart)
			snd_rawmidi_unregister(midi_uart);
		midi_uart = NULL;
		snd_mixer_unregister(mixer);
		mixer = NULL;
#ifdef CONFIG_SND_SEQUENCER
		snd_gus_detach_synthesizer(gus);
#endif
		goto __nodev;
	}
	sprintf(card->longname + strlen(card->longname), " at 0x%x, irq %i, dma %i",
		gus->gf1.port,
		guscard->irqptr->irq,
		guscard->dma1ptr->dma);
	if (guscard->dma2ptr)
		sprintf(card->longname + strlen(card->longname), "&%i",
			guscard->dma2ptr->dma);
	snd_gf1_start(gus);
	if (!snd_card_register(card)) {
		guscard->card = card;
		guscard->gus = gus;
		guscard->pcm = pcm;
		guscard->mixer = mixer;
		guscard->midi_uart = midi_uart;
		return 0;
	}
	snd_gf1_stop(gus);
#ifdef CONFIG_SND_SEQUENCER
	snd_gus_detach_synthesizer(gus);
#endif
	snd_mixer_unregister(mixer);
	mixer = NULL;
	snd_pcm_unregister(pcm);
	pcm = NULL;

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

int init_module(void)
{
	int dev, cards;
	struct snd_gusclassic *guscard;

	for (dev = cards = 0; dev < SND_CARDS && snd_port[dev] > 0; dev++) {
		guscard = (struct snd_gusclassic *)
				snd_kcalloc(sizeof(struct snd_gusclassic), GFP_KERNEL);
		if (!guscard)
			continue;
		if (snd_gusclassic_probe(dev, guscard) < 0) {
			snd_kfree(guscard);
			if (snd_port[dev] == SND_AUTO_PORT)
				break;
			snd_printk("GUS Classic soundcard #%i not found at 0x%x or device busy\n", dev + 1, snd_port[dev]);
			continue;
		}
		snd_gusclassic_cards[dev] = guscard;
		cards++;
	}
	if (!cards) {
		snd_printk("GUS Classic soundcard #%i not found or device busy\n", dev + 1);
		return -ENODEV;
	}
	return 0;
}

void cleanup_module(void)
{
	int idx;
	struct snd_gusclassic *guscard;
	snd_pcm_t *pcm;

	for (idx = 0; idx < SND_CARDS; idx++) {
		guscard = snd_gusclassic_cards[idx];
		if (guscard) {
			snd_card_unregister(guscard->card);
			if (guscard->gus) {
				snd_gf1_stop(guscard->gus);
				snd_gus_init_dma_irq(guscard->gus, 0);
			}
#ifdef CONFIG_SND_SEQUENCER
			snd_gus_detach_synthesizer(guscard->gus);
#endif
			if (guscard->midi_uart)
				snd_rawmidi_unregister(guscard->midi_uart);
			if (guscard->mixer)
				snd_mixer_unregister(guscard->mixer);
			if (guscard->pcm) {
				pcm = guscard->pcm;
				guscard->pcm = NULL;	/* turn off interrupts */
				snd_pcm_unregister(pcm);
			}
			snd_card_free(guscard->card);
			snd_kfree(guscard);
		}
	}
}
