/*
 *  Detection routines for GF1 chip and for AMD InterWave (tm) 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.
 *
 */

#include "driver.h"
#include "pcm.h"
#include "mixer.h"
#include "gus.h"

#define DETECT_GUSP( port, x ) (port + SND_g_u_s_##x)

static void snd_d_gf1_write8(unsigned short port,
			     unsigned char reg,
                             unsigned char val)
{
	unsigned long flags;

	snd_cli(&flags);
	outb(reg, DETECT_GUSP(port, GF1REGSEL));
	outb(val, DETECT_GUSP(port, GF1DATAHIGH));
	snd_sti(&flags);
}

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

	snd_cli(&flags);
	outb(reg, DETECT_GUSP(port, GF1REGSEL));
	result = inb(DETECT_GUSP(port, GF1DATAHIGH));
	snd_sti(&flags);
	return result;
}

static void snd_d_gf1_poke(unsigned short port,
			   unsigned long addr,
                           unsigned char value)
{
	unsigned long flags;

	snd_cli(&flags);
	outb(0x43, DETECT_GUSP(port, GF1REGSEL));
	outw((unsigned short) addr, DETECT_GUSP(port, GF1DATALOW));
	outb(0x44, DETECT_GUSP(port, GF1REGSEL));
	outb((unsigned char) (addr >> 16), DETECT_GUSP(port, GF1DATAHIGH));
	outb(value, DETECT_GUSP(port, DRAM));
	snd_sti(&flags);
}

static unsigned char snd_d_gf1_peek(unsigned short port, unsigned long addr)
{
	unsigned long flags;
	unsigned char result;

	snd_cli(&flags);
	outb(0x43, DETECT_GUSP(port, GF1REGSEL));
	outw((unsigned short) addr, DETECT_GUSP(port, GF1DATALOW));
	outb(0x44, DETECT_GUSP(port, GF1REGSEL));
	outb((unsigned char) (addr >> 16), DETECT_GUSP(port, GF1DATAHIGH));
	result = inb(DETECT_GUSP(port, DRAM));
	snd_sti(&flags);
	return result;
}

int snd_detect_interwave(snd_card_t * card, char *rname,
                         unsigned int *rtype, unsigned short *rport)
{
	static short possible_ports[] =
	{0x210, 0x220, 0x230, 0x240, 0x250, 0x260, -1};
	unsigned long flags;
	unsigned char tmp, rev1, rev2;
	short port, *pport;

	for (pport = possible_ports; *pport > 0; pport++) {
		port = *pport;

		if (snd_register_ioport(card, port, 16, "InterWave (Adlib/SB compatibility)", NULL) < 0)
			continue;
		if (snd_register_ioport(card, port + 0x100, 12, "InterWave (synthesizer)", NULL) < 0) {
			snd_unregister_ioports(card);
			continue;
		}
		if (snd_register_ioport(card, port + 0x10c, 4, "InterWave (CS4231)", NULL) < 0) {
			snd_unregister_ioports(card);
			continue;
		}
#if 0
		snd_printk("Probing InterWave at 0x%x\n", port);
#endif

		/*
		 * ok.. use only INB at first stage of detection to avoid
		 * trouble with wrong outb(..
		 */

		tmp = inb(DETECT_GUSP(port, MIXCNTRLREG));
		snd_printdd("interwave [0x%x]: MIXCNTRLREG = 0x%x\n", port, tmp);
		if (tmp & 0x80)
			goto __nodev;
		tmp = inb(DETECT_GUSP(port, REGCNTRLS));
		snd_printdd("interwave [0x%x]: REGCNTRLS = 0x%x\n", port, tmp);
		if (tmp & 0x04)
			goto __nodev;
		tmp = inb(DETECT_GUSP(port, GF1PAGE));
		snd_printdd("interwave [0x%x]: GF1PAGE = 0x%x\n", port, tmp);
		if (tmp & 0x60)
			goto __nodev;

		snd_d_gf1_write8(port, SND_GF1_GB_RESET, 0);	/* reset GF1 */
		tmp = snd_d_gf1_look8(port, SND_GF1_GB_RESET);
		if ((tmp & 0x07) != 0)
			goto __nodev;
		snd_delay(16);
		snd_d_gf1_write8(port, SND_GF1_GB_RESET, 1);	/* release reset */
		snd_delay(16);
		tmp = snd_d_gf1_look8(port, SND_GF1_GB_RESET);
		if ((tmp & 0x07) != 1)
			goto __nodev;

		snd_cli(&flags);
		rev1 = snd_d_gf1_look8(port, SND_GF1_GB_VERSION_NUMBER);
		snd_d_gf1_write8(port, SND_GF1_GB_VERSION_NUMBER, ~rev1);
		rev2 = snd_d_gf1_look8(port, SND_GF1_GB_VERSION_NUMBER);
		snd_d_gf1_write8(port, SND_GF1_GB_VERSION_NUMBER, rev1);
		snd_sti(&flags);

		if ((rev1 & 0xf0) == (rev2 & 0xf0) &&
		    (rev1 & 0x0f) != (rev2 & 0x0f)) {
			strcpy(rname, "AMD InterWave");
			*rtype = SND_CARD_TYPE_AMD_INTERWAVE;
			*rport = port;
			return 0;	/* ok.. We have InterWave board */
		}
	      __nodev:
		snd_unregister_ioports(card);
	}
	return -ENODEV;
}

int snd_detect_gus1(snd_card_t * card, char *rname,
                    unsigned int *rtype, unsigned short port)
{
	unsigned long flags;
	unsigned char tmp, val, rev;

	if (snd_register_ioport(card, port, 16, "GF1 (Adlib/SB compatibility)", NULL) < 0)
		return -ENODEV;
	if (snd_register_ioport(card, port + 0x100, 12, "GF1 (synthesizer)", NULL) < 0)
		return -ENODEV;
	if (snd_register_ioport(card, port + 0x506, 1, "GUS (control)", NULL) < 0)
		return -ENODEV;

	/*
	 * ok.. use only INB at first stage of detection to avoid
	 * trouble with wrong outb(..
	 */

	tmp = inb(DETECT_GUSP(port, REGCNTRLS));
	snd_printdd("gus [0x%x]: REGCNTRLS = 0x%x\n", port, tmp);
	if (tmp & 0x80)
		return -ENODEV;
#if 0
	tmp = inb(DETECT_GUSP(port, MIDISTAT));
	snd_printdd("gus [0x%x]: MIDISTAT = 0x%x\n", port, tmp);
	if (tmp & 0x4c)
		return -ENODEV;
#endif

	snd_d_gf1_write8(port, SND_GF1_GB_RESET, 0);	/* reset GF1 */
	tmp = snd_d_gf1_look8(port, SND_GF1_GB_RESET);
	if ((tmp & 0x07) != 0)
		return -ENODEV;
	snd_delay(16);
	snd_d_gf1_write8(port, SND_GF1_GB_RESET, 1);	/* release reset */
	snd_delay(16);
	tmp = snd_d_gf1_look8(port, SND_GF1_GB_RESET);
	if ((tmp & 0x07) != 1)
		return -ENODEV;

	snd_d_gf1_poke(port, 0L, 0xaa);
	snd_d_gf1_poke(port, 1L, 0x55);
	if (snd_d_gf1_peek(port, 0L) != 0xaa || snd_d_gf1_peek(port, 1L) != 0x55)
		return -ENODEV;

	snd_cli(&flags);
	outb(0x20, DETECT_GUSP(port, REGCNTRLS));
	val = inb(DETECT_GUSP(port, REGCNTRLS));
	rev = inb(DETECT_GUSP(port, BOARDVERSION));
	snd_sti(&flags);
	snd_printdd("GF1 [0x%x] init - val = 0x%x, rev = 0x%x\n", port, val, rev);
	strcpy(rname, "Gravis UltraSound Classic (2.4)");
	*rtype = SND_CARD_TYPE_GUS_CLASSIC;
	if ((val != 255 && (val & 0x06)) || (rev >= 5 && rev != 255)) {
		if (rev >= 5 && rev <= 9) {
			rname[27] = '3';
			rname[29] = rev == 5 ? '5' : '7';
		}
		if (rev >= 10 && rev != 255) {
			if (rev >= 10 && rev <= 11) {
				strcpy(rname, "Gravis UltraSound MAX");
				*rtype = SND_CARD_TYPE_GUS_MAX;
			} else if (rev == 0x30) {
				strcpy(rname, "Gravis UltraSound Ace");
				*rtype = SND_CARD_TYPE_GUS_ACE;
			} else if (rev == 0x50) {
				strcpy(rname, "Gravis UltraSound Extreme");
				*rtype = SND_CARD_TYPE_GUS_EXTREME;
			} else {
				snd_printk("unknown GF1 revision number at 0x%x - 0x%x (0x%x)\n", port, rev, val);
				snd_printk("  please - report to <perex@suse.cz>\n");
			}
		}
	}
	return 0;
}

int snd_detect_gus(snd_card_t * card, char *rname,
                   unsigned int *rtype, unsigned short *rport)
{
	static short possible_ports[] =
	{0x210, 0x220, 0x230, 0x240, 0x250, 0x260, -1};
	short port, *pport;

	for (pport = possible_ports; *pport > 0; pport++) {
		port = *rport = *pport;

		if (!snd_detect_gus1(card, rname, rtype, port))
			return 0;

		snd_unregister_ioports(card);
	}
	return -ENODEV;
}
