/*
 *  Driver for SoundBlaster 16/AWE32/AWE64 soundcards
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.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.
 *
 */
#include "driver.h"
#include "sb.h"
#include "mpu401.h"
#include "opl3.h"
//#include "emu8000.h"
#include "initval.h"

extern int snd_index[SND_CARDS];
extern char *snd_id[SND_CARDS];
extern int snd_port[SND_CARDS];
extern int snd_mpu_port[SND_CARDS];
extern int snd_awe_port[SND_CARDS];
extern int snd_irq[SND_CARDS];
extern int snd_dma8[SND_CARDS];
extern int snd_dma8_size[SND_CARDS];
extern int snd_dma16[SND_CARDS];
extern int snd_dma16_size[SND_CARDS];
extern int snd_mic_agc[SND_CARDS];

static struct snd_sb16 {
	int use_count;
	snd_irq_t *irqptr;
	snd_dma_t *dma8ptr;
	snd_dma_t *dma16ptr;
	snd_card_t *card;
	snd_pcm_t *pcm;
	snd_kmixer_t *mixer;
	snd_rawmidi_t *rmidi;
	snd_synth_t *synth;
	snd_synth_t *awe;
#ifdef __QNXNTO__
} *snd_sb16_cards[SND_CARDS] = SND_DEFAULT_PTR;
#else
} *snd_sb16_cards[SND_CARDS];
#endif

static snd_pcm_t *snd_sb16_detect(int dev,
				  snd_card_t * card,
				  unsigned short port,
				  snd_irq_t *irqptr,
				  snd_dma_t *dma8ptr,
				  snd_dma_t *dma16ptr)
{
	snd_pcm_t *pcm;
	snd_pcm1_t *pcm1;
	sbdsp_t *codec;
	int mpu_flag = 0;

	if (snd_register_ioport(card, port, 16, "Sound Blaster 16", NULL) < 0)
		return NULL;
	if (snd_mpu_port[dev] == 0x300 || snd_mpu_port[dev] == 0x330) {
		if (snd_register_ioport(card, snd_mpu_port[dev], 2,
					"Sound Blaster 16 - MPU-401", NULL) < 0) {
			snd_printd("sb16: port 0x%x isn't free for MPU-401, midi port is disabled\n", snd_mpu_port[dev]);
		} else {
			mpu_flag = 1;
		}
	}
	if (snd_awe_port[dev] >= 0x620 && snd_awe_port[dev] < 0x6a0) {
		if (snd_register_ioport(card, snd_awe_port[dev],
					4, "Sound Blaster AWE", NULL) < 0 ||
		    snd_register_ioport(card, snd_awe_port[dev] + (0xa40 - 0x640),
		    			4, "Sound Blaster AWE", NULL) < 0 ||
		    snd_register_ioport(card, snd_awe_port[dev] + (0xe40 - 0x640),
		    			4, "Sound Blaster AWE", NULL) < 0) {
			snd_printd("sb16: EMU8000 ports aren't available\n");
			snd_unregister_ioports(card);
			return NULL;
		}
	} else {
		snd_awe_port[dev] = -1;
	}
	pcm = snd_sbdsp_new_device(card, port, irqptr,
				   dma8ptr, dma16ptr, SB_HW_16);
	if (!pcm) {
		snd_unregister_ioports(card);
		return NULL;
	}
	pcm1 = (snd_pcm1_t *) pcm->private_data;
	codec = (sbdsp_t *) pcm1->private_data;
	if (codec->hardware != SB_HW_16) {
		snd_printdd("SB 16 soundcard weren't detected at 0x%x\n", port);
__return1:
		snd_pcm_free(pcm);
		snd_unregister_ioports(card);
		return NULL;
	}
	if (mpu_flag)
		codec->mpu_port = snd_mpu_port[dev];
	if (snd_sbdsp_sb16_configure(pcm) < 0) {
		goto __return1;
	}
	return pcm;
}

static void snd_sb16_interrupt(void *dev_id)
{
	struct snd_sb16 *acard;
	snd_pcm_t *pcm;
	snd_pcm1_t *pcm1;
	sbdsp_t *sbdsp;
	sbmixer_t *mixer;
	register unsigned short status;

	acard = (struct snd_sb16 *) dev_id;
	if (acard == NULL)
		return;
	pcm = acard->pcm;
	if (pcm == NULL)
		return;
	pcm1 = (snd_pcm1_t *) pcm->private_data;
	if (pcm1 == NULL)
		return;
	sbdsp = (sbdsp_t *) pcm1->private_data;
	mixer = &sbdsp->mixer;
	snd_mutex_lock(mixer, mixer);
	status = snd_sbmixer_read(&sbdsp->mixer, 0x82);
	snd_mutex_unlock(mixer, mixer);
	if (status & 0x03)
		snd_sbdsp_sb16_interrupt(pcm, status);
#ifdef SND_TEST_SB_OLD_MIDI
	if (acard->rmidi)
		snd_sbdsp_midi_interrupt(acard->rmidi);
#else
	if ((status & 0x04) && acard->rmidi)
		snd_mpu401_uart_interrupt(acard->rmidi);
#endif
	return;
}

static int snd_sb16_resources(int dev, struct snd_sb16 *acard, snd_card_t * card)
{
	static int possible_irqs[] = {5, 9, 10, 7, -1};
	static int possible_dmas8[] = {1, 3, 0, -1};
	static int possible_dmas16[] = {5, 6, 7, -1};
	int ok = 0, err;

	if ((err = snd_register_interrupt(card, "Sound Blaster 16", snd_irq[dev],
			SND_IRQ_TYPE_ISA, snd_sb16_interrupt, acard, possible_irqs, &acard->irqptr)) < 0)
		return err;
	if ((snd_dma8[dev] >= 0 && snd_dma8[dev] <= 3) ||
	    snd_dma8[dev] == SND_AUTO_DMA) {
		if ((err = snd_register_dma_channel(card,
				"Sound Blaster 16", snd_dma8[dev],
				SND_DMA_TYPE_ISA, snd_dma8_size[dev],
				possible_dmas8, &acard->dma8ptr)) < 0) {
			return err;
		}
		ok++;
	} else {
		acard->dma8ptr = NULL;
	}
	if ((snd_dma16[dev] >= 4 && snd_dma16[dev] <= 7) ||
	    snd_dma16[dev] == SND_AUTO_DMA) {
		if ((err = snd_register_dma_channel(card,
				"Sound Blaster 16", snd_dma16[dev],
				SND_DMA_TYPE_ISA, snd_dma16_size[dev],
				possible_dmas16, &acard->dma16ptr)) < 0) {
			return err;
		}
		ok++;
	} else {
		acard->dma16ptr = NULL;
	}
	if (!ok) {
		snd_printd("SB16 - bad DMA #\n");
		return -ENOMEM;
	}
	return 0;
}

static int snd_sb16_probe(int dev, struct snd_sb16 *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_pcm1_t *pcm1;
	snd_kmixer_t *mixer = NULL;
	snd_rawmidi_t *rmidi = NULL;
	snd_synth_t *synth = NULL;
	snd_synth_t *awe = NULL;

	card = snd_card_new(snd_index[dev], snd_id[dev]);
	if (!card)
		return -ENOMEM;
	card->static_data = acard;
	card->type = SND_CARD_TYPE_SB_16;	/* overriden in probe function */
	if (snd_sb16_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_sb16_detect(dev, card, *ports,
					      acard->irqptr,
					      acard->dma8ptr,
					      acard->dma16ptr);
			if (pcm)
				break;
		}
		if (!pcm) {
			snd_card_free(card);
			return -ENODEV;
		}
	} else {
		pcm = snd_sb16_detect(dev, card, snd_port[dev],
				      acard->irqptr,
				      acard->dma8ptr,
				      acard->dma16ptr);
		if (!pcm) {
			snd_card_free(card);
			return -ENODEV;
		}
	}
	pcm1 = (snd_pcm1_t *) pcm->private_data;
	codec = (sbdsp_t *) pcm1->private_data;
	mixer = snd_sbdsp_new_mixer(card, &codec->mixer, codec->hardware);
	if (!mixer)
		goto __nodev;
#if 0
	synth = snd_opl3_new_device(card, codec->port, codec->port + 2,
				    OPL3_HW_OPL3, 0);
	if (snd_awe_port[dev] > 0) {
		awe = snd_emu8000_new_device(card, snd_awe_port[dev]);
		if (!awe) {
			snd_printd("sbawe: fatal error - EMU-8000 synthesizer not detected at 0x%x\n", snd_awe_port[dev]);
			goto __nodev;
		}
	}
#endif
#ifndef SND_TEST_SB_OLD_MIDI
	if (codec->mpu_port) {
		rmidi = snd_mpu401_uart_new_device(card, MPU401_HW_SB,
						   codec->mpu_port,
						   acard->irqptr->irq);
		if (!rmidi)
			goto __nodev;
	}
#else
	rmidi = snd_sbdsp_midi_new_device(card, pcm);
	if (!rmidi)
		goto __nodev;
#endif

	if (synth && snd_synth_register(synth) < 0)
		goto __nodev;
	if (awe && snd_synth_register(awe) < 0) {
		if (synth)
			snd_synth_unregister(synth);
		synth = NULL;
		goto __nodev;
	}

	if (rmidi && snd_rawmidi_register(rmidi, 0) < 0) {
		if (synth)
			snd_synth_unregister(synth);
		synth = NULL;
		if (awe)
			snd_synth_unregister(awe);
		awe = NULL;
		goto __nodev;
	}

	if (snd_mixer_register(mixer, 0) < 0) {
		if (synth)
			snd_synth_unregister(synth);
		synth = NULL;
		if (awe)
			snd_synth_unregister(awe);
		awe = NULL;
		if (rmidi)
			snd_rawmidi_unregister(rmidi);
		rmidi = NULL;
		goto __nodev;
	}
	if (snd_pcm_register(pcm, 0)) {
		if (synth)
			snd_synth_unregister(synth);
		synth = NULL;
		if (awe)
			snd_synth_unregister(awe);
		awe = NULL;
		if (rmidi)
			snd_rawmidi_unregister(rmidi);
		rmidi = NULL;
		if ( mixer)
			snd_mixer_unregister(mixer);
		mixer = NULL;
		goto __nodev;
	}
	/* setup Mic AGC */
	snd_sbmixer_write(&codec->mixer, SB_DSP4_MIC_AGC, (snd_sbmixer_read(&codec->mixer, SB_DSP4_MIC_AGC) & 0x01) | (snd_mic_agc[dev] ? 0x00 : 0x01));

	snd_enable_irq(acard->irqptr);
	strcpy(card->abbreviation, "SB16");
	strcpy(card->shortname, "Sound Blaster 16");
	sprintf(card->longname, "%s at 0x%x, irq %i, dma ",
		codec->name,
		codec->port,
		acard->irqptr->irq);
	if (acard->dma8ptr)
		sprintf(card->longname + strlen(card->longname), "%i",
			acard->dma8ptr->dma);
	if (acard->dma16ptr)
		sprintf(card->longname + strlen(card->longname), "%s%i",
			acard->dma8ptr != NULL ? "&" : "",
			acard->dma16ptr->dma);
	if (!snd_card_register(card)) {
		acard->card = card;
		acard->pcm = pcm;
		acard->mixer = mixer;
		acard->rmidi = rmidi;
		acard->synth = synth;
		acard->awe = awe;
		return 0;
	}
	snd_synth_unregister(synth);
	synth = NULL;
	snd_synth_unregister(awe);
	awe = NULL;
	snd_rawmidi_unregister(rmidi);
	rmidi = NULL;
	snd_mixer_unregister(mixer);
	mixer = NULL;
	snd_pcm_unregister(pcm);
	pcm = NULL;
__nodev:
	if (synth)
		snd_synth_free(synth);
	if (awe)
		snd_synth_free(awe);
	if (rmidi)
		snd_rawmidi_free(rmidi);
	if (mixer)
		snd_mixer_free(mixer);
	if (pcm)
		snd_pcm_free(pcm);
	snd_card_free(card);
	return -ENXIO;
}

static int snd_sb16_free(int dev)
{
	struct snd_sb16 *acard;

	acard = snd_sb16_cards[dev];
	snd_sb16_cards[dev] = NULL;
	if (acard) {
		snd_card_unregister(acard->card);
		if (acard->synth)
			snd_synth_unregister(acard->synth);
		if (acard->awe)
			snd_synth_unregister(acard->awe);
		if (acard->rmidi)
			snd_rawmidi_unregister(acard->rmidi);
		if (acard->mixer)
			snd_mixer_unregister(acard->mixer);
		if (acard->pcm) {
			snd_pcm_unregister(acard->pcm);
		}
		snd_card_free(acard->card);
		snd_free(acard, sizeof(struct snd_sb16));
	}
	return 0;
}

int snd_sb16_init(void)
{
	int dev, cards;
	struct snd_sb16 *acard;

	for (dev = cards = 0; dev < SND_CARDS && snd_port[dev] > 0; dev++) {
		acard = (struct snd_sb16 *) snd_malloc(sizeof(struct snd_sb16));
		if (!acard)
			continue;
		memset(acard, 0, sizeof(struct snd_sb16));
		if (snd_sb16_probe(dev, acard) < 0) {
			snd_free(acard, sizeof(struct snd_sb16));
			if (snd_port[dev] == SND_AUTO_PORT)
				break;
			snd_printd("Sound Blaster 16 soundcard #%i not found at 0x%x or device busy\n", dev + 1, snd_port[dev]);
			continue;
		}
		snd_sb16_cards[dev] = acard;
		cards++;
	}
	if (!cards) {
		snd_printd("Sound Blaster 16 soundcard #%i not found or device busy\n", dev + 1);
		return -ENODEV;
	}
	return 0;
}

void snd_sb16_cleanup(void)
{
	int dev;

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