/*
 *  Main detection routines
 *  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 "../include/driver.h"
#include "../include/info.h"
#include <stdarg.h>

#define SND_DETECT_VERSION	"1.1"
#define SND_DETECT_BUFFER_SIZE	(32 * 1024)

typedef int (*snd_isa_probe) (snd_card_t * card, char *name,
                              unsigned int *type, unsigned short *port);

#ifdef CONFIG_SND_GUS
extern int snd_detect_interwave(snd_card_t * card, char *name,
                                unsigned int *type, unsigned short *port);
extern int snd_detect_gus(snd_card_t * card, char *name,
                          unsigned int *type, unsigned short *port);
#endif
#ifdef CONFIG_SND_ES1688
extern int snd_detect_es1688(snd_card_t * card, char *name,
                             unsigned int *type, unsigned short *port);
#endif
#ifdef CONFIG_SND_SB_DSP
extern int snd_detect_sb(snd_card_t * card, char *name,
                         unsigned int *type, unsigned short *port);
#endif
#ifdef CONFIG_SND_OPTI9XX
extern int snd_detect_opti9xx(snd_card_t *card, char *name,
                         unsigned int *type, unsigned short *port);
#endif

static snd_info_entry_t *snd_detect_info_entry = NULL;

static snd_isa_probe snd_isa_probes[] =
{
#ifdef CONFIG_SND_ES1688
	snd_detect_es1688,
#endif
#ifdef CONFIG_SND_GUS
	snd_detect_interwave,
	snd_detect_gus,
#endif
#ifdef CONFIG_SND_SB_DSP
	snd_detect_sb,
#endif
#ifdef CONFIG_SND_OPTI9XX
	snd_detect_opti9xx,
#endif
};

#define SND_DETECT_ISA_COUNT (sizeof(snd_isa_probes)/sizeof(void *))

#ifdef CONFIG_PCI

struct snd_pci_probe {
	unsigned int vendor;
	unsigned int device;
	unsigned int type;
	char *name;
};

static struct snd_pci_probe snd_pci_probes[] =
{
	{0x1013, 0x6001, SND_CARD_TYPE_CS461X, "Sound Fusion CS4610"},
	{0x1013, 0x6003, SND_CARD_TYPE_CS461X, "Sound Fusion CS4612"},
	{0x1013, 0x6004, SND_CARD_TYPE_CS461X, "Sound Fusion CS4615"},
	{0x1023, 0x2000, SND_CARD_TYPE_TRID4DWAVEDX, "Trident 4DWave PCI DX"},
	{0x1023, 0x2001, SND_CARD_TYPE_TRID4DWAVENX, "Trident 4DWave PCI NX"},
	{0x125d, 0x1969, SND_CARD_TYPE_ESS_ES1938, "ESS Solo-1 ES1938"},
	{0x1274, 0x5000, SND_CARD_TYPE_ENS1370, "Ensoniq AudioPCI ES1370"},
	{0x1274, 0x1371, SND_CARD_TYPE_ENS1371, "Ensoniq AudioPCI ES1371"},
	{0x5333, 0xca00, SND_CARD_TYPE_S3_SONICVIBES, "S3 SonicVibes"}
};

#define SND_DETECT_PCI_COUNT \
			(sizeof(snd_pci_probes)/sizeof(struct snd_pci_probe))

#endif

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

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

static void snd_detect_read(snd_info_buffer_t * buffer, void *private_data)
{
	int idx, cardidx, limit;
	snd_card_t *cards[SND_CARDS];	/* detected cards */
	char name[64];
	unsigned int type;
	unsigned short port;
	struct pci_dev *pcidev;

	buffer->curr = buffer->buffer;
	*buffer->buffer = 0;
	snd_iprintf(buffer, "Version: " SND_DETECT_VERSION "\n");
	if (snd_cards_count > 0) {
		buffer->error = -EBUSY;
		return;
	}
	limit = snd_ecards_limit;
	snd_ecards_limit = SND_CARDS;
	memset(&cards, 0, sizeof(cards));
	cardidx = 0;
	for (idx = 0; idx < SND_CARDS; idx++) {
		cards[idx] = snd_card_new(idx + 1, NULL,
					  snd_detect_use_inc,
					  snd_detect_use_dec);
		if (!cards[idx]) {
			for (idx = 0; idx < SND_CARDS; idx++) {
				if (cards[idx])
					snd_card_free(cards[idx]);
			}
			buffer->error = -ENOMEM;
			snd_ecards_limit = limit;
			return;
		}
	}
	for (idx = 0; cardidx < SND_CARDS && idx < SND_DETECT_ISA_COUNT; idx++) {
		while (!snd_isa_probes[idx] (cards[cardidx], name, &type, &port)) {
			snd_iprintf(buffer, "ISA#0x%x#%s#0x%x\n", type, name, port);
			cardidx++;
		}
	}
	for (idx = 0; idx < SND_CARDS; idx++) {
		if (cards[idx])
			snd_card_free(cards[idx]);
	}
	pcidev = NULL;
#ifdef CONFIG_PCI
	for (idx = 0; idx < SND_DETECT_PCI_COUNT; idx++) {
		pcidev = pci_find_device(snd_pci_probes[idx].vendor,
					 snd_pci_probes[idx].device,
					 pcidev);
		if (pcidev == NULL)
			continue;
		do {
			snd_iprintf(buffer, "PCI#0x%x#%s#%i#%i#%i\n",
					snd_pci_probes[idx].type,
					snd_pci_probes[idx].name,
					pcidev->bus->number,
					PCI_SLOT(pcidev->devfn),
					PCI_FUNC(pcidev->devfn));
		} while ((pcidev = pci_find_device(snd_pci_probes[idx].vendor,
						   snd_pci_probes[idx].device,
						   pcidev)) != NULL);
	}
#endif
	snd_ecards_limit = limit;
}

static int snd_detect_info_init(void)
{
	snd_info_entry_t *entry;

	entry = snd_info_create_entry(NULL, "detect");
	if (!entry)
		return -ENOMEM;
	entry->t.text.read_size = PAGE_SIZE;
	entry->t.text.read = snd_detect_read;
	if (snd_info_register(entry) < 0) {
		snd_info_free_entry(entry);
		return -ENOMEM;
	}
	snd_detect_info_entry = entry;
	return 0;
}

static int snd_detect_info_done(void)
{
	if (snd_detect_info_entry)
		snd_info_unregister(snd_detect_info_entry);
	return 0;
}

int init_module(void)
{
	return snd_detect_info_init();
}

void cleanup_module(void)
{
	snd_detect_info_done();
}
