/*
 *  Routines for Gravis UltraSound 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_MAIN_OBJECT_FILE
#include "driver.h"
#include "gus.h"
#include "control.h"


void snd_gus_use_inc(snd_gus_card_t * gus)
{
	MOD_INC_USE_COUNT;
	if (gus->card && gus->card->use_inc)
		gus->card->use_inc(gus->card);
}

void snd_gus_use_dec(snd_gus_card_t * gus)
{
	MOD_DEC_USE_COUNT;
	if (gus->card && gus->card->use_dec)
		gus->card->use_dec(gus->card);
}

static int snd_gus_get_game_switch(snd_card_t * card,
				   snd_kswitch_t * kswitch,   
				   snd_switch_t * uswitch)
{
	snd_gus_card_t *gus = (snd_gus_card_t *) kswitch->private_data;
  
	uswitch->type = SND_SW_TYPE_BYTE;
	uswitch->low = 0;
	uswitch->high = 31;
	uswitch->value.data8[0] = gus->joystick_dac & 31;
        return 0;
}

static int snd_gus_set_game_switch(snd_card_t * card,
				   snd_kswitch_t * kswitch,
				   snd_switch_t * uswitch)
{
        snd_gus_card_t *gus = (snd_gus_card_t *) kswitch->private_data;
	int change = 0;

        if (uswitch->type != SND_SW_TYPE_BYTE ||
            uswitch->value.data8[0] > 31)
                return -EINVAL;
	change = gus->joystick_dac != uswitch->value.data8[0];
	gus->joystick_dac = uswitch->value.data8[0];
	snd_gf1_i_write8(gus, SND_GF1_GB_JOYSTICK_DAC_LEVEL, gus->joystick_dac);
        return change;
}

static snd_kswitch_t snd_gus_game_switch =
{
	SND_CTL_SW_JOYSTICK_SPEED,
	(snd_get_switch_t *)snd_gus_get_game_switch,
	(snd_get_switch_t *)snd_gus_set_game_switch,
	0,
	NULL,
	NULL
};

void snd_gus_init_control(snd_gus_card_t *gus)
{
	snd_card_t *card;
	
	card = gus->card;
	if (card->type != SND_CARD_TYPE_GUS_ACE)
		snd_control_switch_new(card, &snd_gus_game_switch, gus);
}

/*
 *
 */

static void snd_gus_free_card(void *ptr)
{
	snd_gus_card_t *gus = (snd_gus_card_t *) ptr;
	snd_free(gus, sizeof(snd_gus_card_t));
}

snd_gus_card_t *snd_gus_new_card(snd_card_t * card,
				 unsigned short port,
				 snd_irq_t * irqptr,
				 snd_dma_t * dma1ptr,
				 snd_dma_t * dma2ptr,
				 int timer_dev)
{
	snd_gus_card_t *gus;

	gus = (snd_gus_card_t *) snd_calloc(sizeof(snd_gus_card_t));
	if (!gus)
		return NULL;
	gus->card = card;
	gus->gf1.port = port;
	gus->gf1.irqptr = irqptr;
	gus->gf1.dma1ptr = dma1ptr;
	if (dma2ptr) {
		gus->gf1.dma2ptr = dma2ptr;
	} else {
		gus->gf1.dma2ptr = dma1ptr;
		gus->equal_dma = 1;
	}
	gus->timer_dev = timer_dev;
	snd_spin_prepare(gus, reg);
	snd_spin_prepare(gus, active_voice);
	snd_spin_prepare(gus, playback);
	snd_spin_prepare(gus, dma);
	snd_spin_prepare(gus, pcm_volume_level);
	snd_spin_prepare(gus, uart_cmd);
	snd_spin_prepare(gus, neutral);
	snd_mutex_prepare(gus, dma);
	snd_sleep_prepare(gus, neutral);
	snd_sleep_prepare(gus, clear);
	card->private_data = gus;
	card->private_free = snd_gus_free_card;
	return gus;
}

int snd_gus_set_port(snd_gus_card_t * gus, unsigned short port)
{
	gus->gf1.port = port;
	/* fill register variables for speedup */
	gus->gf1.reg_page = GUSP(gus, GF1PAGE);
	gus->gf1.reg_regsel = GUSP(gus, GF1REGSEL);
	gus->gf1.reg_data8 = GUSP(gus, GF1DATAHIGH);
	gus->gf1.reg_data16 = GUSP(gus, GF1DATALOW);
	gus->gf1.reg_irqstat = GUSP(gus, IRQSTAT);
	gus->gf1.reg_dram = GUSP(gus, DRAM);
	gus->gf1.reg_timerctrl = GUSP(gus, TIMERCNTRL);
	gus->gf1.reg_timerdata = GUSP(gus, TIMERDATA);
	/* ---- */
	return 0;
}

/*
 *  Memory detection routine for plain GF1 soundcards
 */

int snd_gus_detect_memory(snd_gus_card_t * gus)
{
	int l, idx, local;
	unsigned char d;

	snd_gf1_poke(gus, 0L, 0xaa);
	snd_gf1_poke(gus, 1L, 0x55);
	if (snd_gf1_peek(gus, 0L) != 0xaa || snd_gf1_peek(gus, 1L) != 0x55) {
		snd_printk("snd: plain GF1 card at 0x%x without onboard DRAM?\n", gus->gf1.port);
		return -ENOMEM;
	}
	for (idx = 1, d = 0xaa; idx < 4; idx++, d++) {
		local = idx << 18;
		snd_gf1_poke(gus, local, d);
		snd_gf1_poke(gus, local + 1, d + 1);
		if (snd_gf1_peek(gus, local) != d ||
		    snd_gf1_peek(gus, local + 1) != d + 1 ||
		    snd_gf1_peek(gus, 0L) != 0xaa)
			break;
	}
#if 1
	gus->gf1.memory = idx << 18;
#else
	gus->gf1.memory = 256 * 1024;
#endif
	for (l = 0, local = gus->gf1.memory; l < 4; l++, local -= 256 * 1024) {
		gus->gf1.mem_alloc.banks_8[l].address =
		    gus->gf1.mem_alloc.banks_8[l].size = 0;
		gus->gf1.mem_alloc.banks_16[l].address = l << 18;
		gus->gf1.mem_alloc.banks_16[l].size = local > 0 ? 256 * 1024 : 0;
	}
	gus->gf1.mem_alloc.banks_8[0].size = gus->gf1.memory;
	return 0;		/* some memory were detected */
}

int snd_gus_init_dma_irq(snd_gus_card_t * gus, int latches)
{
	snd_card_t *card;
	unsigned long flags;
	unsigned char i, irq, dma;
	static unsigned char irqs[16] =
		{0, 0, 1, 3, 0, 2, 0, 4, 0, 1, 0, 5, 6, 0, 0, 7};
	static unsigned char dmas[8] =
		{6, 1, 0, 2, 0, 3, 4, 5};

	if (!gus)
		return -EINVAL;
	card = gus->card;
	if (!card)
		return -EINVAL;

	gus->mix_cntrl_reg &= 0xf8;
	gus->mix_cntrl_reg |= 0x01;	/* disable MIC, LINE IN, enable LINE OUT */
	if (gus->codec_flag || gus->ess_flag) {
		gus->mix_cntrl_reg &= ~1;	/* enable LINE IN */
		gus->mix_cntrl_reg |= 4;	/* enable MIC */
	}
	dma = dmas[gus->gf1.dma1ptr->dma & 7];
	i = dmas[gus->gf1.dma2ptr->dma & 7];
#if 0
	printk("dma1 = %i, dma2 = %i\n", gus->gf1.dma1, gus->gf1.dma2);
#endif
	if (gus->equal_dma || !i) {
		if (!i)
			snd_printk("GUS: Warning! DMA2 isn't defined.\n");
		dma |= 0x40;
	} else
		dma |= i << 3;

	if ((dma & 7) == 0) {
		snd_printk("GUS: Error! DMA isn't defined.\n");
		return -EINVAL;
	}
	irq = irqs[gus->gf1.irqptr->irq & 0x0f];
	if (!irq) {
		snd_printk("GUS: Error! IRQ isn't defined.\n");
		return -EINVAL;
	}
	irq |= 0x40;
#if 0
	card->mixer.mix_ctrl_reg |= 0x10;
#endif

	snd_spin_lock(gus, reg, &flags);
	outb(5, GUSP(gus, REGCNTRLS));
	outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
	outb(0x00, GUSP(gus, IRQDMACNTRLREG));
	outb(0, GUSP(gus, REGCNTRLS));
	snd_spin_unlock(gus, reg, &flags);

	snd_gf1_delay(gus);

	snd_spin_lock(gus, reg, &flags);
	outb(0x00 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
	outb(dma, GUSP(gus, IRQDMACNTRLREG));
	if (latches) {
		outb(0x40 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
		outb(irq, GUSP(gus, IRQDMACNTRLREG));
	}
	snd_spin_unlock(gus, reg, &flags);

	snd_gf1_delay(gus);

	snd_spin_lock(gus, reg, &flags);
	outb(0x00 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
	outb(dma, GUSP(gus, IRQDMACNTRLREG));
	if (latches) {
		outb(0x40 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
		outb(irq, GUSP(gus, IRQDMACNTRLREG));
	}
	snd_spin_unlock(gus, reg, &flags);

	snd_gf1_delay(gus);

	if (latches)
		gus->mix_cntrl_reg |= 0x08;	/* enable latches */
	else
		gus->mix_cntrl_reg &= ~0x08;	/* disable latches */
	snd_spin_lock(gus, reg, &flags);
	outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG));
	outb(0, GUSP(gus, GF1PAGE));
	snd_spin_unlock(gus, reg, &flags);

	return 0;
}

int snd_gus_check_version(snd_gus_card_t * gus)
{
	unsigned long flags;
	unsigned char val, rev;
	snd_card_t *card;

	card = gus->card;
	snd_spin_lock(gus, reg, &flags);
	outb(0x20, GUSP(gus, REGCNTRLS));
	val = inb(GUSP(gus, REGCNTRLS));
	rev = inb(GUSP(gus, BOARDVERSION));
	snd_spin_unlock(gus, reg, &flags);
	snd_printdd("GF1 [0x%x] init - val = 0x%x, rev = 0x%x\n", gus->gf1.port, val, rev);
	strcpy(card->abbreviation, "GUS");
	strcpy(card->longname, "Gravis UltraSound Classic (2.4)");
	card->type = SND_CARD_TYPE_GUS_CLASSIC;
	if ((val != 255 && (val & 0x06)) || (rev >= 5 && rev != 255)) {
		if (rev >= 5 && rev <= 9) {
			gus->ics_flag = 1;
			if (rev == 5)
				gus->ics_flipped = 1;
			card->longname[27] = '3';
			card->longname[29] = rev == 5 ? '5' : '7';
		}
		if (rev >= 10 && rev != 255) {
			if (rev >= 10 && rev <= 11) {
				strcpy(card->longname, "Gravis UltraSound MAX");
				card->type = SND_CARD_TYPE_GUS_MAX;
			} else if (rev == 0x30) {
				strcpy(card->longname, "Gravis UltraSound Ace");
				card->type = SND_CARD_TYPE_GUS_ACE;
			} else if (rev == 0x50) {
				strcpy(card->longname, "Gravis UltraSound Extreme");
				card->type = SND_CARD_TYPE_GUS_EXTREME;
			} else {
				snd_printk("unknown GF1 revision number at 0x%x - 0x%x (0x%x)\n", gus->gf1.port, rev, val);
				snd_printk("  please - report to <perex@suse.cz>\n");
			}
		}
	}
	strcpy(card->shortname, card->longname);
	gus->uart_enable = 1;	/* standard GUSses doesn't have midi uart trouble */
	snd_gus_init_control(gus);
	return 0;
}

/*
 *  INIT part
 */

#ifndef LINUX_2_1
extern struct symbol_table snd_symbol_table_gus_export;
#endif

int init_module(void)
{
#ifndef LINUX_2_1
	if (register_symtab(&snd_symbol_table_gus_export) < 0)
		return -ENOMEM;
#endif
	return 0;
}

void cleanup_module(void)
{
}
