/*
 *  Driver for Gravis UltraSound MAX 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_OSS_COMPAT__
#define SND_MAIN_OBJECT_FILE
#include "driver.h"
#include "gus.h"
#include "cs4231.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,0x230,0x240,0x250,0x260 */
int snd_irq[SND_CARDS] = SND_DEFAULT_IRQ;	/* 2,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) */
#ifdef MODULE_PARM
MODULE_PARM(snd_index, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_index, "Index value for GUS MAX soundcard.");
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SND_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for GUS MAX soundcard.");
MODULE_PARM(snd_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_port, "Port # for GUS MAX driver.");
MODULE_PARM(snd_irq, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_irq, "IRQ # for GUS MAX driver.");
MODULE_PARM(snd_dma1, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma1, "DMA1 # for GUS MAX driver.");
MODULE_PARM(snd_dma2, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma2, "DMA2 # for GUS MAX driver.");
MODULE_PARM(snd_dma1_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma1_size, "DMA1 size in kB for GUS MAX driver.");
MODULE_PARM(snd_dma2_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma2_size, "DMA2 size in kB for GUS MAX 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 MAX driver.");
#endif

struct snd_gusmax {
	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, *pcm1;
	snd_kmixer_t *mixer;
	snd_rawmidi_t *midi_uart;
	unsigned short gus_status_reg;
	unsigned short pcm_status_reg;
};

static struct snd_gusmax *snd_gusmax_cards[SND_CARDS] = SND_DEFAULT_PTR;

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

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

static int snd_gusmax_detect(snd_gus_card_t * gus, unsigned short port)
{
	if (snd_register_ioport(gus->card, port, 16,
				"GUS MAX (Adlib/SB compatibility)", NULL) < 0)
		return -EBUSY;
	if (snd_register_ioport(gus->card, port + 0x100, 12,
				"GUS MAX (synthesizer)", NULL) < 0) {
		snd_unregister_ioports(gus->card);
		return -EBUSY;
	}
	if (snd_register_ioport(gus->card, port + 0x10c, 4,
				"GUS MAX (CS4231)", NULL) < 0) {
		snd_unregister_ioports(gus->card);
		return -EBUSY;
	}
	if (snd_register_ioport(gus->card, port + 0x506, 1,
				"GUS MAX (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_MAX) {
		snd_printdd("GUS MAX 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_gusmax_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	struct snd_gusmax *maxcard = (struct snd_gusmax *) dev_id;
	unsigned short status;
	int loop, max = 100;

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

	do {
		loop = 0;
		if ((status = inb(maxcard->gus_status_reg))) {
			snd_gus_interrupt(maxcard->gus, status);
			loop++;
		}
		if ((status = inb(maxcard->pcm_status_reg)) & 0x01) {	/* IRQ bit is set? */
			snd_cs4231_interrupt(maxcard->pcm, status);
			loop++;
		}
	} while (loop && --max > 0);
}

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

	if ((err = snd_register_interrupt(card, "GUS MAX",
				snd_irq[dev], SND_IRQ_TYPE_ISA,
				snd_gusmax_interrupt, maxcard,
				possible_irqs, &maxcard->irqptr)) < 0)
		return err;
	if ((err = snd_register_dma_channel(card,
				"GF1/CS4231 record", snd_dma1[dev],
				SND_DMA_TYPE_ISA, snd_dma1_size[dev],
				possible_dmas, &maxcard->dma1ptr)) < 0)
		return err;
	if ((snd_dma2[dev] >= 0 && snd_dma2[dev] <= 7) ||
	    snd_dma2[dev] == SND_AUTO_DMA) {
		if ((err = snd_register_dma_channel(card,
				"CS4231 playback", snd_dma2[dev],
				SND_DMA_TYPE_ISA, snd_dma2_size[dev],
				possible_dmas, &maxcard->dma2ptr)) < 0)
			return err;
	} else {
		maxcard->dma2ptr = NULL;
	}
	return 0;
}

static void snd_gusmax_init(int dev, snd_card_t * card, snd_gus_card_t * gus)
{
	gus->equal_irq = 1;
	gus->codec_flag = 1;
	gus->max_flag = 1;
	gus->joystick_dac = snd_joystick_dac[dev];
	/* init control register */
	gus->max_cntrl_val = (gus->gf1.port >> 4) & 0x0f;
	if (gus->gf1.dma1ptr->dma > 3)
		gus->max_cntrl_val |= 0x10;
	if (gus->gf1.dma2ptr->dma > 3)
		gus->max_cntrl_val |= 0x20;
	gus->max_cntrl_val |= 0x40;
	outb(gus->max_cntrl_val, GUSP(gus, MAXCNTRLPORT));
}

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

static int snd_gusmax_mixer(snd_kmixer_t * mixer)
{
	/* reassign AUX0 to SYNTHESIZER */
	if (snd_mixer_group_rename(mixer,
				SND_MIXER_IN_AUX, 0,
				SND_MIXER_IN_SYNTHESIZER, 0) < 0)
		goto __error;
	if (snd_mixer_element_rename(mixer,
				SND_MIXER_IN_AUX, 0, SND_MIXER_ETYPE_INPUT,
				SND_MIXER_IN_SYNTHESIZER, 0) < 0)
		goto __error;
	if (snd_mixer_element_rename(mixer,
				"Aux Input Volume", 0, SND_MIXER_ETYPE_VOLUME1,
				"Synth Input Volume", 0) < 0)
		goto __error;
	if (snd_mixer_element_rename(mixer,
				"Aux Input Switch", 0, SND_MIXER_ETYPE_SWITCH1,
				"Synth Input Switch", 0) < 0)
		goto __error;
	/* reassign AUX1 to CD */
	if (snd_mixer_group_rename(mixer,
				SND_MIXER_IN_AUX, 1,
				SND_MIXER_IN_CD, 0) < 0)
		goto __error;
	if (snd_mixer_element_rename(mixer,
				SND_MIXER_IN_AUX, 1, SND_MIXER_ETYPE_INPUT,
				SND_MIXER_IN_CD, 0) < 0)
		goto __error;
	if (snd_mixer_element_rename(mixer,
				"Aux Input Volume", 1, SND_MIXER_ETYPE_VOLUME1,
				"CD Input Volume", 0) < 0)
		goto __error;
	if (snd_mixer_element_rename(mixer,
				"Aux Input Switch", 1, SND_MIXER_ETYPE_SWITCH1,
				"CD Input Switch", 0) < 0)
		goto __error;
#if 0
	/* reassign Mono Input to MIC */
	if (snd_mixer_group_rename(mixer,
				SND_MIXER_IN_MONO, 0,
				SND_MIXER_IN_MIC, 0) < 0)
		goto __error;
	if (snd_mixer_element_rename(mixer,
				SND_MIXER_IN_MONO, 0, SND_MIXER_ETYPE_INPUT,
				SND_MIXER_IN_MIC, 0) < 0)
		goto __error;
	if (snd_mixer_element_rename(mixer,
				"Mono Input Volume", 0, SND_MIXER_ETYPE_VOLUME1,
				"MIC Input Volume", 0) < 0)
		goto __error;
	if (snd_mixer_element_rename(mixer,
				"Mono Input Switch", 0, SND_MIXER_ETYPE_SWITCH1,
				"MIC Input Switch", 0) < 0)
		goto __error;
#endif
	return 0;

      __error:
      	return -EINVAL;
}

static int snd_gusmax_probe(int dev, struct snd_gusmax *maxcard)
{
	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_pcm_t *pcm1 = 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_gusmax_use_inc, snd_gusmax_use_dec);
	if (!card)
		return -ENOMEM;
	if (snd_gusmax_resources(dev, maxcard, card) < 0) {
		snd_card_free(card);
		return -EBUSY;
	}
	gus = snd_gus_new_card(card,
			       snd_port[dev],
			       maxcard->irqptr,
			       maxcard->dma1ptr,
			       maxcard->dma2ptr,
			       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_gusmax_detect(gus, *ports))
				break;
		}
		if (*ports < 0) {
			goto __nodev;
		}
	} else {
		if (snd_gusmax_detect(gus, snd_port[dev])) {
			snd_card_free(card);
			return -ENODEV;
		}
	}
	maxcard->gus_status_reg = gus->gf1.reg_irqstat;
	maxcard->pcm_status_reg = gus->gf1.port + 0x10c + 2;
	snd_gusmax_init(dev, card, gus);
	if (snd_gus_init_dma_irq(gus, 1) < 0) {
		snd_card_free(card);
		return -EINVAL;
	}
	pcm = snd_cs4231_new_device(card,
				    gus->gf1.port + 0x10c,
				    maxcard->irqptr,
				    maxcard->dma2ptr == NULL ?
				    	maxcard->dma1ptr : maxcard->dma2ptr,
				    maxcard->dma1ptr,
				    CS4231_HW_DETECT,
				    2);
	if (!pcm)
		goto __nodev;
	mixer = snd_cs4231_new_mixer(pcm, 0);
	if (!mixer)
		goto __nodev;
	element = snd_mixer_element_find(mixer, SND_MIXER_IN_AUX, 0, SND_MIXER_ETYPE_INPUT);
	if (!element)
		goto __nodev;
	pcm1 = snd_gf1_pcm_new_device(gus, mixer, element, 1);
	if (!pcm1)
		goto __nodev;
	if (snd_gusmax_mixer(mixer) < 0)
		goto __nodev;

	midi_uart = snd_gf1_rawmidi_new_device(gus);
	if (!midi_uart)
		goto __nodev;

#ifdef CONFIG_SND_SEQUENCER
	snd_gus_attach_synthesizer(gus);
#endif
	if (snd_rawmidi_register(midi_uart, 0) < 0) {
#ifdef CONFIG_SND_SEQUENCER
		snd_gus_detach_synthesizer(gus);
#endif
		goto __nodev;
	}
	if (snd_mixer_register(mixer, 0) < 0) {
#ifdef CONFIG_SND_SEQUENCER
		snd_gus_detach_synthesizer(gus);
#endif
		snd_rawmidi_unregister(midi_uart);
		midi_uart = NULL;
		goto __nodev;
	}
	if (snd_pcm_register(pcm, 0) < 0) {
#ifdef CONFIG_SND_SEQUENCER
		snd_gus_detach_synthesizer(gus);
#endif
		snd_rawmidi_unregister(midi_uart);
		midi_uart = 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
		snd_rawmidi_unregister(midi_uart);
		midi_uart = NULL;
		snd_mixer_unregister(mixer);
		mixer = NULL;
		snd_pcm_unregister(pcm);
		pcm = NULL;
		goto __nodev;
	}
	sprintf(card->longname + strlen(card->longname), " at 0x%x, irq %i, dma %i",
		gus->gf1.port,
		maxcard->irqptr->irq,
		maxcard->dma1ptr->dma);
	if (maxcard->dma2ptr)
		sprintf(card->longname + strlen(card->longname), "&%i",
			maxcard->dma2ptr->dma);
	snd_gf1_start(gus);
	if (!snd_card_register(card)) {
		maxcard->card = card;
		maxcard->gus = gus;
		maxcard->pcm = pcm;
		maxcard->pcm1 = pcm1;
		maxcard->mixer = mixer;
		maxcard->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(pcm1);
	pcm1 = NULL;
	snd_pcm_unregister(pcm);
	pcm = NULL;

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

int init_module(void)
{
	int dev, cards;
	struct snd_gusmax *maxcard;

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

void cleanup_module(void)
{
	int idx;
	struct snd_gusmax *maxcard;
	snd_pcm_t *pcm;

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