/*
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *  Detection routines for ESS ESx688/488 chip
 *
 *
 *   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 "es1688.h"

#define DES1688P( port, reg ) ( port + e_s_s_ESS1688##reg )

extern int snd_detect_gus1(snd_card_t * card, char *rname,
                           unsigned int *rtype, unsigned short port);

static int snd_es1688_ddsp_command(unsigned short port, unsigned char val)
{
	int i;

	for (i = 10000; i; i--)
		if ((inb(DES1688P(port, STATUS)) & 0x80) == 0) {
			outb(val, DES1688P(port, COMMAND));
			return 1;
		}
	snd_printd("snd_es1688_dsp_command: timeout (0x%x)\n", val);
	return 0;
}

#if 0

static int snd_es1688_ddsp_get_byte(unsigned short port)
{
	int i;

	for (i = 1000; i; i--)
		if (inb(DES1688P(port, DATA_AVAIL)) & 0x80)
			return inb(DES1688P(port, READ));
	PRINTD("snd es1688 get byte failed: 0x%x = 0x%x!!!\n", DES1688P(port, DATA_AVAIL), inb(DES1688P(port, DATA_AVAIL)));
	return -ENODEV;
}

static int snd_es1688_dwrite(unsigned short port,
                             unsigned char reg, unsigned char data)
{
	if (!snd_es1688_ddsp_command(port, reg))
		return 0;
	return snd_es1688_ddsp_command(port, data);
}

static int snd_es1688_dread(unsigned short port, unsigned char reg)
{
	/* Read a byte from an extended mode register of ES1688 */
	if (!snd_es1688_ddsp_command(port, 0xc0))
		return -1;
	if (!snd_es1688_ddsp_command(port, reg))
		return -1;
	return snd_es1688_ddsp_get_byte(port);
}

#endif

static void snd_es1688_dmixer_write(unsigned short port,
			            unsigned char reg, unsigned char data)
{
	unsigned long flags;

	snd_cli(&flags);
	outb(reg, DES1688P(port, MIXER_ADDR));
	snd_delay(1);
	outb(data, DES1688P(port, MIXER_DATA));
	snd_delay(1);
	snd_sti(&flags);
}

#if 0

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

	snd_cli(&flags);
	outb(reg, DES1688P(port, MIXER_ADDR));
	snd_delay(1);
	result = inb(DES1688P(port, MIXER_DATA));
	snd_delay(1);
	snd_sti(&flags);
	return result;
}

#endif

static int snd_es1688_reset(unsigned short port)
{
	int i;

	outb(3, DES1688P(port, RESET));		/* valid only for ESS chips, SB -> 1 */
	snd_delay(1);
	outb(0, DES1688P(port, RESET));
	snd_delay(3);
	for (i = 0; i < 1000 && !(inb(DES1688P(port, DATA_AVAIL)) & 0x80); i++);
	if (inb(DES1688P(port, READ)) != 0xaa) {
		snd_printdd("ess_reset: failed!!!\n");
		return -ENODEV;
	}
	snd_es1688_ddsp_command(port, 0xc6);	/* enable extended mode */
	return 0;
}

int snd_detect_es1688(snd_card_t * card, char *rname, unsigned int *rtype, unsigned short *rport)
{
	static short possible_ports[] =
	{0x220, 0x240, 0x260, -1};
	unsigned long flags;
	unsigned short major, minor, version;
	short *pport, port;
	int i, gport;

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

		if (snd_register_ioport(card, port, 16, "ESS AudioDrive", NULL) < 0)
			continue;

		/*
		 *  initialization sequence
		 */

		snd_cli(&flags);	/* Some ESS1688 cards need this */
		inb(DES1688P(port, ENABLE1));
		inb(DES1688P(port, ENABLE1));
		inb(DES1688P(port, ENABLE1));
		inb(DES1688P(port, ENABLE2));
		inb(DES1688P(port, ENABLE1));
		inb(DES1688P(port, ENABLE2));
		inb(DES1688P(port, ENABLE1));
		inb(DES1688P(port, ENABLE1));
		inb(DES1688P(port, ENABLE2));
		inb(DES1688P(port, ENABLE1));
		inb(DES1688P(port, ENABLE0));
		snd_sti(&flags);

		if (snd_es1688_reset(port) < 0) {
			snd_printdd("ESS: [0x%x] reset failed... 0x%x\n", port, inb(DES1688P(port, READ)));
			goto __nodev;
		}
		snd_es1688_ddsp_command(port, 0xe7);	/* return identification */

		for (i = 1000, major = minor = 0; i; i--) {
			if (inb(DES1688P(port, DATA_AVAIL)) & 0x80) {
				if (major == 0) {
					major = inb(DES1688P(port, READ));
				} else {
					minor = inb(DES1688P(port, READ));
				}
			}
		}

		snd_printdd("ESS: [0x%x] found.. major = 0x%x, minor = 0x%x\n", port, major, minor);

		version = (major << 8) | minor;
		if (!version)
			goto __nodev;	/* probably SB */
		switch (version & 0xfff0) {
		case 0x4880:
			strcpy(rname, "SoundBlaster Pro (ESS AudioDrive ES488)");
			*rtype = SND_CARD_TYPE_SB_PRO;
			return 0;
		case 0x6880:
			goto __es1688;
		default:
			snd_printk("detect: ESS AudioDrive chip found at 0x%x, but version 0x%x isn't known\n", port, version);
			goto __nodev;
		}

	      __es1688:

		/*
		 *  check if we have GUS Extreme, don't try initialize other ESS cards
		 */

		i = inb(DES1688P(port, INIT1));
		snd_printdd("ESS: [0x%x] (1) init1 = 0x%x\n", port, i);
		if (!(i & 0x40))
			goto __plain_es1688;
		outb(0, DES1688P(port, INIT1));
		i = inb(DES1688P(port, INIT1));
		snd_printdd("ESS: [0x%x] (2) init1 = 0x%x\n", port, i);
		if (i != 0x40)
			goto __plain_es1688;

		/*
		 * 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_es1688_dmixer_write(port, 0x40, 0x8b);

		for (gport = 0x210; gport < 0x270; gport += 0x20)
			if (snd_check_ioport(card, gport, 16) >= 0)
				break;

		if (gport > 0x270)
			goto __plain_es1688;

		snd_cli(&flags);
		outb(gport & 0x0040 ? 2 : 0, DES1688P(port, INIT1));
		outb(0, 0x201);
		outb(gport & 0x0020 ? 2 : 0, DES1688P(port, INIT1));
		outb(0, 0x201);
		outb(gport & 0x0010 ? 3 : 1, DES1688P(port, INIT1));
		snd_sti(&flags);

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

		snd_unregister_ioports(card);
		if (snd_register_ioport(card, port, 16, "ESS AudioDrive", NULL) < 0)
			continue;

	      __plain_es1688:
		strcpy(rname, "ESS AudioDrive ES1688");
		*rtype = SND_CARD_TYPE_ESS_ES1688;
		return 0;

	      __nodev:
		snd_unregister_ioports(card);
	}

	return -ENODEV;
}
