/*
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *  Detection routines for Sound Blaster cards
 *
 *
 *   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 "sb.h"

#define DSB( port, reg ) ( port + s_b_SB_##reg )

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

	for (i = 10000; i; i--)
		if ((inb(DSB(port, STATUS)) & 0x80) == 0) {
			outb(val, DSB(port, COMMAND));
			return 1;
		}
#ifdef ULTRACFG_DEBUG
	printk("snd_sb_dsp_command: timeout (0x%x)\n", val);
#endif
	return 0;
}

#if 0

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

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

#endif

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

	outb(1, DSB(port, RESET));
	snd_delay(1);
	outb(0, DSB(port, RESET));
	snd_delay(3);
	for (i = 0; i < 1000 && !(inb(DSB(port, DATA_AVAIL)) & 0x80); i++);
	if (inb(DSB(port, READ)) != 0xaa) {
#ifdef ULTRACFG_DEBUG
		snd_printk("sb_reset: failed!!!\n");
#endif
		return -ENODEV;
	}
	return 0;
}

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

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

		if (snd_register_ioport(card, port, 16, "Sound Blaster", NULL) < 0)
			continue;

		/*
		 *  initialization sequence
		 */

		if (snd_sb_reset(port) < 0) {
			snd_printdd("SB: [0x%x] reset failed... 0x%x\n", port, inb(DSB(port, READ)));
			goto __nodev;
		}
		snd_sb_dsp_command(port, SB_DSP_GET_VERSION);	/* return identification */

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

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

		version = (major << 8) | minor;
		if (!version)
			goto __nodev;	/* probably SB */
		switch (major) {
		case 1:
			strcpy(rname, "Sound Blaster 1.0");
			*rtype = SND_CARD_TYPE_SB_10;
			break;
		case 2:
			strcpy(rname, minor > 1 ? "Sound Blaster 2.01+" : "Sound Blaster 2.0");
			*rtype = SND_CARD_TYPE_SB_20;
			break;
		case 3:
			strcpy(rname, "Sound Blaster Pro");
			*rtype = SND_CARD_TYPE_SB_PRO;
			break;
		case 4:
			strcpy(rname, "Sound Blaster 16");
			*rtype = SND_CARD_TYPE_SB_16;
			break;
		default:
			snd_printk("detect: Sound Blaster DSP chip found at 0x%x, but version 0x%x isn't known\n", port, version);
			goto __nodev;
		}
		sprintf(str, " (%i.%i)", major, minor);
		strcat(rname, str);
		return 0;

	      __nodev:
		snd_unregister_ioports(card);
	}

	return -ENODEV;
}
