/*
 *  The driver for the EMU10K1 (SB Live!) based soundcards
 *  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/emu10k1.h"
#include "../include/initval.h"

MODULE_DESCRIPTION("\
Driver: EMU10K1\n\
Card: Sound Blaster Live!\n\
Card: Sound Blaster PCI512\n\
Card: E-mu APS\n\
PCI: 0x1102=0x0002\n\
");

#if defined(CONFIG_SND_SEQUENCER) && defined(CONFIG_SND_SYNTH_EMU10K1)
#define ENABLE_SYNTH
#include "../include/emu10k1_synth.h"
#endif

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_dac_frame_size[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 128};
int snd_adc_frame_size[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 64};
int snd_seq_ports[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 4};
int snd_max_synth_voices[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 64};

MODULE_PARM(snd_index, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_index, "Index value for the EMU10K1 soundcard.");
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SND_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for the EMU10K1 soundcard.");
MODULE_PARM(snd_dac_frame_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dac_frame_size, "DAC frame size in kB for the EMU10K1 soundcard.");
MODULE_PARM(snd_adc_frame_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_adc_frame_size, "ADC frame size in kB for the EMU10K1 soundcard.");
MODULE_PARM(snd_seq_ports, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_seq_ports, "Number of sequencer ports for Emu10k1 WaveTable synth.");
MODULE_PARM(snd_max_synth_voices, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_max_synth_voices, "Maximum number of voices for WaveTable");


struct snd_emu10k1 {
	struct pci_dev *pci;
	snd_irq_t *irqptr;
	snd_dma_t *dma1ptr;	/* DAC frame */
	snd_dma_t *dma2ptr;	/* ADC frame */
	snd_card_t *card;
	emu10k1_t *emu;
	snd_pcm_t *pcm;
	snd_pcm_t *pcm_mic;
	snd_pcm_t *pcm_efx;
	snd_kmixer_t *mixer;
	snd_rawmidi_t *midi_uart;
	snd_hwdep_t *synth;
#ifdef ENABLE_SYNTH
	snd_seq_device_t *wave;
#endif	
};

static struct snd_emu10k1 *snd_emu10k1_cards[SND_CARDS] = SND_DEFAULT_PTR;

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

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

static int __init snd_card_emu10k1_detect(snd_card_t * card, struct snd_emu10k1 *scard)
{
	if ((scard->pci = pci_find_device(PCI_VENDOR_ID_CREATIVE,
					  PCI_DEVICE_ID_CREATIVE_EMU10K1,
					  scard->pci)) == NULL)
		return -ENODEV;
	if (snd_register_ioport(card, pci_resource_start(scard->pci, 0), 0x20, "EMU10K1", NULL) < 0)
		return -ENODEV;
	return 0;
}

static void snd_card_emu10k1_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	struct snd_emu10k1 *scard = (struct snd_emu10k1 *) dev_id;
	emu10k1_t *emu;
	unsigned int status;

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

static int __init snd_card_emu10k1_resources(snd_card_t * card,
					     struct snd_emu10k1 *scard,
					     int dev)
{
	int err;

	if ((err = snd_register_interrupt(card,
			"EMU10K1", scard->pci->irq,
			SND_IRQ_TYPE_PCI, snd_card_emu10k1_interrupt,
			scard, NULL, &scard->irqptr)) < 0)
		return err;
	if ((err = snd_register_dma_channel(card,
			"EMU10K1 - DAC frame", 0,
			SND_DMA_TYPE_PCI, snd_dac_frame_size[dev],
			NULL, &scard->dma1ptr)) < 0)
		return err;
	scard->dma1ptr->multi = 1;      /* enable multi-alloc */	        
	if (snd_adc_frame_size[dev] > 64)
		snd_adc_frame_size[dev] = 64;	/* hardware limit */
	if ((err = snd_register_dma_channel(card,
			"EMU10K1 - ADC frame", 2,
			SND_DMA_TYPE_PCI, snd_adc_frame_size[dev],
			NULL, &scard->dma2ptr)) < 0)
		return err;
	return 0;
}

static int __init snd_card_emu10k1_probe(int dev, struct snd_emu10k1 *scard)
{
	snd_card_t *card;
	snd_pcm_t *pcm = NULL;
	snd_pcm_t *pcm_mic = NULL;
	snd_pcm_t *pcm_efx = NULL;
	snd_kmixer_t *mixer = NULL;
	snd_rawmidi_t *midi_uart = NULL;
	snd_hwdep_t *synth = NULL;
#ifdef ENABLE_SYNTH
	snd_seq_device_t *wave = NULL;
#endif

	card = snd_card_new(snd_index[dev], snd_id[dev],
			    snd_card_emu10k1_use_inc, snd_card_emu10k1_use_dec);
	if (card == NULL)
		return -ENOMEM;
	card->type = SND_CARD_TYPE_EMU10K1;
	scard->pci = NULL;
	do {
		if (!snd_card_emu10k1_detect(card, scard))
			break;
	} while (scard->pci);
	if (scard->pci == NULL) {
		snd_card_free(card);
		return -ENODEV;
	}
	if (snd_card_emu10k1_resources(card, scard, dev) < 0) {
		snd_card_free(card);
		return -ENODEV;
	}
	if (snd_emu10k1_create(card, scard->pci,
			       scard->dma1ptr,
			       scard->dma2ptr,
			       scard->irqptr,
			       &scard->emu) < 0)
		goto __nodev;
	if (snd_emu10k1_pcm(scard->emu, 0, &pcm) < 0)
		goto __nodev;
	if (snd_emu10k1_pcm_mic(scard->emu, 1, &pcm_mic) < 0)
		goto __nodev;
	if (snd_emu10k1_pcm_efx(scard->emu, 2, &pcm_efx) < 0)
		goto __nodev;
	if (!scard->emu->APS) {	/* APS board hasn't mixer */
		if (snd_emu10k1_mixer(scard->emu, 0, pcm, &mixer) < 0)
			goto __nodev;
	}
	if (snd_emu10k1_midi(scard->emu, 0, &midi_uart) < 0)
		goto __nodev;
#if 0
	if (snd_opl3_new(card, 0, FM801_REG(scard->emu, OPL3_BANK0),
			 FM801_REG(scard->emu, OPL3_BANK1),
			 OPL3_HW_OPL3_FM801, -1, &synth) < 0)
		goto __nodev;
#endif
#ifdef ENABLE_SYNTH
	if (snd_seq_device_new(card, 1, SND_SEQ_DEV_ID_EMU10K1_SYNTH,
			       sizeof(snd_emu10k1_synth_arg_t), &wave) < 0 ||
	    wave == NULL) {
		snd_printk("can't initialize Emu10k1 wavetable synth\n");
	} else {
		snd_emu10k1_synth_arg_t *arg;
		arg = SND_SEQ_DEVICE_ARGPTR(wave);
		strcpy(wave->name, "Emu-10k1 Synth");
		arg->hwptr = scard->emu;
		arg->index = 1;
		arg->seq_ports = snd_seq_ports[dev];
		arg->max_voices = snd_max_synth_voices[dev];
	}
#endif
 
	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 at 0x%lx, irq %li",
		card->shortname,
		pci_resource_start(scard->pci, 0),
		scard->irqptr->irq);

	if (!snd_card_register(card)) {
		scard->card = card;
		scard->mixer = mixer;
		scard->pcm = pcm;
		scard->pcm_mic = pcm_mic;
		scard->pcm_efx = pcm_efx;
		scard->midi_uart = midi_uart;
		scard->synth = synth;
#ifdef ENABLE_SYNTH
		scard->wave = wave;
#endif
		return 0;
	}
	goto __nodev;

      __nodev:
	snd_card_free(card);
	return -ENXIO;
}

#ifdef MODULE

static int __exit snd_card_emu10k1_free(int dev)
{
	struct snd_emu10k1 *scard;

	scard = snd_emu10k1_cards[dev];
	snd_emu10k1_cards[dev] = NULL;
	if (scard) {
		snd_card_unregister(scard->card);
		snd_kfree(scard);
	}
	return 0;
}

#endif

static int __init alsa_card_emu10k1_init(void)
{
	int dev, cards;
	struct snd_emu10k1 *scard;

	for (dev = cards = 0; dev < SND_CARDS; dev++) {
		scard = (struct snd_emu10k1 *)
				snd_kcalloc(sizeof(struct snd_emu10k1), GFP_KERNEL);
		if (scard == NULL)
			continue;
		if (snd_card_emu10k1_probe(dev, scard) < 0) {
			snd_kfree(scard);
			break;
		}
		snd_emu10k1_cards[dev] = scard;
		cards++;
	}
	if (!cards) {
#ifdef MODULE
		snd_printk("EMU10K1 soundcard #%i not found or device busy\n", dev + 1);
#endif
		return -ENODEV;
	}
	return 0;
}

static void __exit alsa_card_emu10k1_exit(void)
{
	int dev;

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

module_init(alsa_card_emu10k1_init)
module_exit(alsa_card_emu10k1_exit)
