/*
 *  Driver for SoundBlaster 1.0/2.0/Pro soundcards and compatible
 *  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/sb.h"
#include "../include/opl3.h"
#include "../include/initval.h"

MODULE_DESCRIPTION("\
Driver: Sound Blaster 1.0/2.0/Pro\n\
Card: Sound Blaster 1.0\n\
Card: Sound Blaster 2.0\n\
Card: Sound Blaster Pro\n\
");

int snd_index[SND_CARDS] = SND_DEFAULT_IDX;	/* Index 0-MAX */
char *snd_id[SND_CARDS] = SND_DEFAULT_STR;	/* ID for this card */
int snd_port[SND_CARDS] = SND_DEFAULT_PORT;	/* 0x220,0x240,0x260 */
int snd_irq[SND_CARDS] = SND_DEFAULT_IRQ;	/* 5,7,9,10 */
int snd_dma8[SND_CARDS] = SND_DEFAULT_DMA;	/* 1,3 */
int snd_dma8_size[SND_CARDS] = SND_DEFAULT_DMA_SIZE; /* 8,16,32,64,128 */
MODULE_PARM(snd_index, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_index, "Index value for Sound Blaster soundcard.");
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SND_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for Sound Blaster soundcard.");
MODULE_PARM(snd_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_port, "Port # for SB8 driver. [PORT12]");
MODULE_PARM(snd_irq, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_irq, "IRQ # for SB8 driver. [IRQ]");
MODULE_PARM(snd_dma8, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma8, "8-bit DMA # for SB8 driver. [DMA]");
MODULE_PARM(snd_dma8_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma8_size, "8-bit DMA size for SB8 driver. [DMA_SIZE]");

static struct snd_sb8 {
	snd_irq_t *irqptr;
	snd_dma_t *dma8ptr;
	snd_card_t *card;
	snd_pcm_t *pcm;
	snd_kmixer_t *mixer;
	snd_rawmidi_t *rmidi;
	snd_hwdep_t *synth;
} *snd_sb8_cards[SND_CARDS] = SND_DEFAULT_PTR;

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

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

static snd_pcm_t * __init snd_sb8_detect(snd_card_t * card,
					 unsigned short port,
					 snd_irq_t * irqptr,
					 snd_dma_t * dma8ptr)
{
	snd_pcm_t *pcm;
	sbdsp_t *codec;
	int err;

	if (snd_register_ioport(card, port, 16, "Sound Blaster", NULL) < 0)
		return NULL;
	err = snd_sb8dsp_new_pcm(card, 0, port, irqptr, dma8ptr, SB_HW_10, &pcm);
	if (err < 0) {
		snd_unregister_ioports(card);
		return NULL;
	}
	codec = snd_magic_cast(sbdsp_t, pcm->private_data, NULL);
	if (codec->hardware >= SB_HW_16) {
		snd_printk("SB 16 soundcard detected at 0x%x, use SB16 driver\n", port);
		snd_device_free(card, pcm);
		snd_unregister_ioports(card);
		return NULL;
	}
	return pcm;
}

static void snd_sb8_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	struct snd_sb8 *acard;
	snd_pcm_t *pcm;
	sbdsp_t *codec;

	acard = (struct snd_sb8 *) dev_id;
	if (acard == NULL)
		return;
	pcm = acard->pcm;
	if (pcm == NULL)
		return;
	codec = snd_magic_cast(sbdsp_t, pcm->private_data, );
	if (codec == NULL)
		return;
	if (codec->open8 & SB_OPEN_PCM) {
		snd_sb8dsp_interrupt(pcm);
	} else {
		if (acard->rmidi)
			snd_sb8dsp_midi_interrupt(acard->rmidi);
	}
}

static int __init snd_sb8_resources(int dev, struct snd_sb8 *acard, snd_card_t * card)
{
	static long possible_irqs[] = {-1};
	static long possible_dmas8[] = {-1};
	int err;

	if ((err = snd_register_interrupt(card,
			"Sound Blaster", snd_irq[dev],
			SND_IRQ_TYPE_ISA, snd_sb8_interrupt,
			acard, possible_irqs, &acard->irqptr)) < 0)
		return err;
	if (snd_dma8[dev] >= 0) {
		if ((err = snd_register_dma_channel(card,
				"Sound Blaster", snd_dma8[dev],
				SND_DMA_TYPE_ISA, snd_dma8_size[dev],
				possible_dmas8, &acard->dma8ptr)) < 0)
			return err;
	} else {
		return -ENOMEM;
	}
	return 0;
}

static int __init snd_sb8_probe(int dev, struct snd_sb8 *acard)
{
	static int possible_ports[] = {0x220, 0x240, 0x260, -1};
	int *ports = possible_ports;
	sbdsp_t *codec;
	snd_card_t *card;
	snd_pcm_t *pcm = NULL;
	snd_kmixer_t *mixer = NULL;
	snd_rawmidi_t *rmidi = NULL;
	snd_hwdep_t *synth = NULL;
	char *str;

	card = snd_card_new(snd_index[dev], snd_id[dev],
			    snd_sb8_use_inc, snd_sb8_use_dec);
	if (card == NULL)
		return -ENOMEM;
	card->type = SND_CARD_TYPE_SB_10;	/* overriden in probe function */
	if (snd_sb8_resources(dev, acard, card) < 0) {
		snd_card_free(card);
		return -EBUSY;
	}
	pcm = NULL;
	if (snd_port[dev] == SND_AUTO_PORT) {
		for (ports = possible_ports; *ports >= 0; ports++) {
			pcm = snd_sb8_detect(card, *ports, acard->irqptr, acard->dma8ptr);
			if (pcm)
				break;
		}
		if (pcm == NULL) {
			snd_card_free(card);
			return -ENODEV;
		}
	} else {
		pcm = snd_sb8_detect(card, snd_port[dev], acard->irqptr, acard->dma8ptr);
		if (pcm == NULL) {
			snd_card_free(card);
			return -ENODEV;
		}
	}
	codec = snd_magic_cast(sbdsp_t, pcm->private_data, -ENXIO);
	snd_sb8dsp_new_mixer(codec, 0, pcm, &mixer);
	if (codec->hardware == SB_HW_10 || codec->hardware == SB_HW_20) {
		snd_opl3_new(card, 0, codec->port + 8,
			     0, OPL3_HW_AUTO, -1, &synth);
	} else {
		snd_opl3_new(card, 0, codec->port,
			     codec->port + 2, OPL3_HW_AUTO, 0, &synth);
	}

	snd_sb8dsp_midi_new(codec, 0, &rmidi);

	strcpy(card->abbreviation, "SB");
	strcpy(card->shortname, codec->name);
	str = card->shortname;
	while (*str && *str != '(')
		str++;
	if (*str == '(' && str != card->shortname)
		*(str - 1) = 0;
	sprintf(card->longname, "%s at 0x%lx, irq %li, dma %li",
		codec->name,
		codec->port,
		acard->irqptr->irq,
		acard->dma8ptr->dma);
	if (!snd_card_register(card)) {
		acard->card = card;
		acard->pcm = pcm;
		acard->mixer = mixer;
		acard->rmidi = rmidi;
		acard->synth = synth;
		return 0;
	}

	snd_card_free(card);
	return -ENXIO;
}

static int __init alsa_card_sb8_init(void)
{
	int dev, cards;
	struct snd_sb8 *acard;

	for (dev = cards = 0; dev < SND_CARDS && snd_port[dev] > 0; dev++) {
		acard = (struct snd_sb8 *) snd_kcalloc(sizeof(struct snd_sb8), GFP_KERNEL);
		if (acard == NULL)
			continue;
		if (snd_sb8_probe(dev, acard) < 0) {
			snd_kfree(acard);
			if (snd_port[dev] == SND_AUTO_PORT)
				break;
#ifdef MODULE
			snd_printk("Sound Blaster soundcard #%i not found at 0x%x or device busy\n", dev + 1, snd_port[dev]);
#endif
			continue;
		}
		snd_sb8_cards[dev] = acard;
		cards++;
	}
	if (!cards) {
#ifdef MODULE
		snd_printk("Sound Blaster soundcard #%i not found or device busy\n", dev + 1);
#endif
		return -ENODEV;
	}
	return 0;
}

static void __exit alsa_card_sb8_exit(void)
{
	int idx;
	struct snd_sb8 *acard;

	for (idx = 0; idx < SND_CARDS; idx++) {
		acard = snd_sb8_cards[idx];
		if (acard) {
			snd_card_unregister(acard->card);
			snd_kfree(acard);
		}
	}
}

module_init(alsa_card_sb8_init)
module_exit(alsa_card_sb8_exit)
