/*
 *  Driver for sblive soundcard
 *
 */

#include "driver.h"
#include "sndpci.h"
#include "emu10k1.h"
#include "initval.h"

#ifdef __QNXNTO__
int snd_index[SND_CARDS] = SND_DEFAULT_IDX;     /* Index 1-MAX */
char *snd_id[SND_CARDS] = SND_DEFAULT_STR;      /* ID for this card */
int snd_dac_frame_size[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 128};
int snd_adc_frame_size[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 128};
#else
int snd_index[SND_CARDS] = {-1,-1,-1,-1,-1,-1,-1,-1};   /* Index 1-MAX */
char *snd_id[SND_CARDS];;       /* ID for this card */
int snd_dac_frame_size[SND_CARDS] = {128,128,128,128,128,128,128,128};
int snd_adc_frame_size[SND_CARDS] = {128,128,128,128,128,128,128,128};
#endif


struct snd_sblive
{
	struct snd_pci_dev pci;
	snd_irq_t *irqptr;
	snd_dma_t *dma1ptr;					/* DAC1 frame */
	snd_dma_t *dma2ptr;					/* DAC2 frame */
	snd_dma_t *dma3ptr;					/* ADC frame */
	snd_card_t *card;
	emu10k1_t *emu;
	snd_pcm_t *pcm;						/* DAC1/ADC */
	snd_pcm_t *pcm2;					/* DAC2 */
	snd_kmixer_t *mixer;
	snd_rawmidi_t *midi_uart;
};

#ifdef __QNXNTO__
static struct snd_sblive *snd_sblive_cards[SND_CARDS]= SND_DEFAULT_PTR;

#else
static struct snd_sblive *snd_sblive_cards[SND_CARDS];

#endif


static void
snd_card_sblive_interrupt (void *dev_id)
{
	struct snd_sblive *scard= (struct snd_sblive *) dev_id;
	unsigned int status;

	if (!scard || !scard->emu)
		return;
	if ((status = inl(scard->emu->port + IPR)) & 0x00ffffc0)
		snd_emu10k1_interrupt (scard->emu, status);
}

static int
snd_sblive_resources (snd_card_t * card, struct snd_sblive *scard, int dev)
{
	int     err;

	if ((err = snd_register_ioport(card,
			PCI_IO_ADDR(scard->pci.base_address[0]),
			0x40, "sblive", NULL)) < 0) {
		return err;
	}
	if ((err= snd_register_interrupt (card,
				"sblive", scard->pci.irq,
				SND_IRQ_TYPE_PCI, snd_card_sblive_interrupt,
				scard, NULL, &scard->irqptr)) < 0)
	{
		return err;
	}
	if ((err= snd_register_dma_channel (card,
				"sblive - DAC1 frame", 0,
				SND_DMA_TYPE_PCI, snd_dac_frame_size[dev],
				NULL, &scard->dma1ptr)) < 0)
	{
		return err;
	}
	if ((err= snd_register_dma_channel (card,
				"sblive - DAC2 frame", 1,
				SND_DMA_TYPE_PCI, snd_dac_frame_size[dev],
				NULL, &scard->dma2ptr)) < 0)
	{
		return err;
	}
	if ((err= snd_register_dma_channel (card,
				"sblive - ADC frame", 2,
				SND_DMA_TYPE_PCI, snd_adc_frame_size[dev],
				NULL, &scard->dma3ptr)) < 0)
	{
		return err;
	}
	return 0;
}

static int
snd_sblive_probe (int dev, struct snd_sblive *scard, int device, int pci_index)
{
	snd_card_t *card;
	snd_pcm_t *pcm= NULL,
	          *pcm2= NULL;
	snd_kmixer_t *mixer= NULL;
	snd_rawmidi_t *midi_uart= NULL;

	card= snd_card_new (snd_index[dev], snd_id[dev]);
	if (!card)
		return -ENOMEM;

	card->static_data= scard;
	if (snd_sblive_detect (card, &scard->pci, pci_index, device) < 0)
	{
		snd_card_free (card);
		return -ENODEV;
	}

	if (snd_sblive_resources (card, scard, dev) < 0)
	{
		snd_card_free (card);
		return -ENODEV;
	}

	scard->emu= snd_emu10k1_create (card, &scard->pci,
					scard->dma1ptr, 
					scard->dma2ptr,
					scard->dma3ptr,
					scard->irqptr);
	if (!scard->emu)
		goto __nodev;
	mixer= snd_emu10k1_mixer (scard->emu);
	if (!mixer)
		goto __nodev;
	pcm= snd_emu10k1_pcm (scard->emu);
	if (!pcm)
		goto __nodev;
	pcm2= snd_emu10k1_pcm2 (scard->emu);
	if (!pcm2)
		goto __nodev;
	midi_uart= snd_emu10k1_midi (scard->emu);
	if (!midi_uart)
		goto __nodev;

	if (snd_mixer_register (mixer, 0) < 0)
		goto __nodev;
	if (snd_pcm_register (pcm, 0) < 0)
	{
		snd_mixer_unregister (mixer);
		mixer= NULL;
		goto __nodev;
	}
	if (snd_pcm_register (pcm2, 1) < 0)
	{
		snd_pcm_unregister (pcm);
		pcm= NULL;
		snd_mixer_unregister (mixer);
		mixer= NULL;
		goto __nodev;
	}
	if (snd_rawmidi_register(midi_uart, 0) < 0) {
		snd_pcm_unregister(pcm2);
		pcm2 = NULL;
		snd_pcm_unregister(pcm);
		pcm = NULL;
		snd_mixer_unregister(mixer);
		mixer = NULL;
		goto __nodev;
	}

	snd_enable_irq (scard->irqptr);

	strcpy (card->abbreviation, "EMU10k1");
	if (scard->emu->APS) {
		strcpy(card->shortname, "E-mu APS");
	} else {
		strcpy(card->shortname, "Sound Blaster Live!");
	}
	sprintf (card->longname, "%s %s at 0x%lx, irq %i",
		card->shortname,
		"EMU10k1",
		PCI_IO_ADDR (scard->pci.base_address[0]),
		scard->irqptr->irq);

	if (!snd_card_register (card))
	{
		scard->card= card;
		scard->mixer= mixer;
		scard->pcm= pcm;
		scard->pcm2= pcm2;
		scard->midi_uart = scard->emu->rmidi = midi_uart;
		return 0;
	}
	goto __nodev;

  __nodev:
	if (midi_uart)
		snd_rawmidi_free(midi_uart);
	if (mixer)
		snd_mixer_free (mixer);
	if (pcm)
		snd_pcm_free (pcm);
	if (pcm2)
		snd_pcm_free (pcm2);
	if (scard->emu)
	{
		snd_emu10k1_free (scard->emu);
		scard->emu= NULL;
	}
	snd_card_free (card);
	return -ENXIO;
}


int
snd_sblive_init (void)
{
	int     dev, cards= 0;
	struct snd_sblive *scard= NULL;

	for (dev= 0; dev < SND_CARDS; dev++)
	{
		if (!scard)
		{
			scard= (struct snd_sblive *) snd_malloc (sizeof (struct snd_sblive));

			if (!scard)
				continue;
			memset (scard, 0, sizeof (struct snd_sblive));
		}
		if (snd_sblive_probe (cards, scard, SND_CARD_TYPE_SBLIVE, dev) == 0)
		{
			snd_sblive_cards[cards]= scard;
			snd_printd ("Found EMU10k1 #%d\n", cards);
			cards++;
			scard= NULL;
			if (cards > SND_CARDS)
				return 0;
		}
		else
			break;
	}
	snd_free (scard, sizeof (struct snd_sblive));

	if (!cards)
	{
		snd_printk ("SoundBlaster Live! soundcard #%i not found or device busy\n", dev + 1);
		return -ENODEV;
	}
	return 0;
}

static int
snd_card_sblive_free (int dev)
{
	struct snd_sblive *scard;
	snd_pcm_t *pcm;

	scard= snd_sblive_cards[dev];
	snd_sblive_cards[dev]= NULL;
	if (scard)
	{
		snd_card_unregister (scard->card);
		if (scard->midi_uart) {
			scard->emu->rmidi = NULL;
			snd_rawmidi_unregister(scard->midi_uart);
		}
		if (scard->mixer)
			snd_mixer_unregister (scard->mixer);
		if (scard->pcm)
		{
			pcm= scard->pcm;
			scard->pcm= NULL;	   /* turn off interrupts */
			snd_pcm_unregister (pcm);
		}
		if (scard->pcm2)
		{
			pcm= scard->pcm2;
			scard->pcm2= NULL;	   /* turn off interrupts */
			snd_pcm_unregister (pcm);
		}
		if (scard->emu)
		{
			snd_emu10k1_free (scard->emu);
			scard->emu= NULL;
		}
		snd_card_free (scard->card);
		snd_free (scard, sizeof (struct snd_sblive));
	}
	return 0;
}


void
snd_sblive_cleanup (void)
{
	int     dev;

	for (dev= 0; dev < SND_CARDS; dev++)
		snd_card_sblive_free (dev);
}
