/*
 *  Driver for ESS Maestro 1/2/2E Sound Card (started 21.8.99)
 *  Copyright (c) by Matze Braun <MatzeBraun@gmx.de>.
 *                  
 *  Most of the driver code comes from Zach Brown(zab@redhat.com)
 *	Alan Cox OSS Driver
 *  Rewritted from card-es1938.c source.
 *
 *
 *   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/es1968.h"
#include "../include/initval.h"

MODULE_DESCRIPTION("\
Driver: ESS Maestro\n\
Card: Generic ESS Maestro 2e Chipset\n\
Card: Generic ESS Maestro 2 Chipset\n\
Card: Generic ESS Maestro 1 Chipset\n\
Card: Terratech DMX\n\
PCI: 0x125d=0x1978\n\
PCI: 0x125d=0x1968\n\
PCI: 0x1285=0x0100\n\
");

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)] = 32 };
int snd_adc_frame_size[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 32 };
int snd_gesbuf[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 1024 };
int snd_midi_enable[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 0 };
int snd_pcm_channels_p[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 4 };
int snd_pcm_channels_c[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 1 };
MODULE_PARM(snd_index, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_index, "Index value for ESS Maestro soundcard.");
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SND_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for ESS Maestro soundcard.");
MODULE_PARM(snd_gesbuf, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_gesbuf, "total Buffer Size in kB. [range=1-4096]");
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 ESS Maestro soundcard. [range=1-32]");
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 ESS Maestro soundcard. [range=1-32]");
MODULE_PARM(snd_midi_enable, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_midi_enable,
		 "Midi enabled for ESS Maestro soundcard. [BOOL]");
MODULE_PARM(snd_pcm_channels_p, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_pcm_channels_p,
		 "PCM Playback subchannels for ESS Maestro soundcard. [range=1-8]");
MODULE_PARM(snd_pcm_channels_c, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_pcm_channels_c,
		 "PCM Capture subchannels for ESS Maestro soundcard. [range=0-8]");

struct snd_es1968 {
	struct pci_dev *pci;
	snd_irq_t *irqptr;
	snd_dma_t *dma1ptr;
	snd_card_t *card;
	es1968_t *maestro;
	snd_pcm_t *pcm;
	snd_kmixer_t *mixer;
	snd_rawmidi_t *midi_uart;
	int type;
};

static struct snd_es1968 *snd_es1968_cards[SND_CARDS] = SND_DEFAULT_PTR;

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

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

static int __init snd_es1968_detect(snd_card_t * card, struct snd_es1968 *scard,
				    unsigned short vendor, unsigned short device)
{
	if ((scard->pci = pci_find_device(vendor,
					  device,
					  scard->pci)) == NULL)
		    return -ENODEV;

	/* don't pick up weird modem maestros */
	if (((scard->pci->class >> 8) & 0xffff) != PCI_CLASS_MULTIMEDIA_AUDIO)
		return -ENODEV;

	if (snd_register_ioport(card, pci_resource_start(scard->pci, 0), 0x100,
			        "ESS Maestro", NULL) < 0)
		goto __nodev;

	return 0;
      __nodev:
	snd_unregister_ioports(card);
	return -ENODEV;
}

static void snd_es1968_interrupt2(int irq, void *dev_id, struct pt_regs *regs)
{
	struct snd_es1968 *scard = (struct snd_es1968 *) dev_id;

	if (!scard || !scard->maestro)
		return;

	snd_es1968_interrupt(scard->maestro);
}

static int __init snd_es1968_resources(snd_card_t * card, struct snd_es1968 *scard,
				       int dev)
{
	int err;

	/* Registering DMA & IRQ */
	if ((err =
	     snd_register_interrupt(card, "ESS Maestro", scard->pci->irq,
				    SND_IRQ_TYPE_PCI, snd_es1968_interrupt2,
				    scard, NULL, &scard->irqptr)) < 0)
		return err;

	if ((err =
	     snd_register_dma_channel(card, "ESS Maestro - 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 */
       	scard->dma1ptr->addressbits = 28;	/* 256Mb */

	return 0;
}

static int __init snd_es1968_probe(int dev, struct snd_es1968 *scard)
{
	snd_card_t *card;
	snd_kmixer_t *mixer = NULL;
	snd_pcm_t *pcm = NULL;
	snd_rawmidi_t *midi_uart = NULL;

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

	scard->pci = NULL;
	do {
		if (!snd_es1968_detect(card, scard, PCI_VENDOR_ID_ESS,
				       PCI_DEVICE_ID_ESS_M2E)) {
			card->type = SND_CARD_TYPE_ESS_ES1978;
			break;
		}
	} while (scard->pci);
	if (scard->pci == NULL) {
		do {
			if (!snd_es1968_detect(card, scard, PCI_VENDOR_ID_ESS,
					       PCI_DEVICE_ID_ESS_M2)) {
				card->type = SND_CARD_TYPE_ESS_ES1968;
				break;
			}
		} while (scard->pci);
	}
	if (scard->pci == NULL) {
		do {
			if (!snd_es1968_detect(card, scard, PCI_VENDOR_ID_ESS_OLD,
					       PCI_DEVICE_ID_ESS_M1)) {
				card->type = SND_CARD_TYPE_ESS_ESOLDM1;
				break;
			}
		} while (scard->pci);
	}
	if (scard->pci == NULL) {
		snd_card_free(card);
		return -ENODEV;
	}

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

	if (snd_es1968_create(card,
			      scard->pci, scard->dma1ptr, scard->irqptr,
                              snd_midi_enable[dev], snd_gesbuf[dev],
                              snd_pcm_channels_p[dev], 
                              snd_pcm_channels_c[dev], &scard->maestro) < 0)
		goto __nodev;

	if (snd_es1968_pcm(scard->maestro, 0, &pcm) < 0)
		goto __nodev;

	if (snd_es1968_mixer(scard->maestro, 0, pcm, &mixer) < 0)
		goto __nodev;

	if (snd_midi_enable[dev])
		if (snd_es1968_midi(scard->maestro, 0, &midi_uart) < 0)
			goto __nodev;

	switch (card->type) {
	case SND_CARD_TYPE_ESS_ES1978:
		strcpy(card->abbreviation, "ESM2E");
		strcpy(card->shortname, "ESS Maestro 2E");
		break;
	case SND_CARD_TYPE_ESS_ES1968:
		strcpy(card->abbreviation, "ESM2");
		strcpy(card->shortname, "ESS Maestro 2");
		break;
	case SND_CARD_TYPE_ESS_ESOLDM1:
		strcpy(card->abbreviation, "ESM1");
		strcpy(card->shortname, "ESS Maestro 1");
		break;
	}
	sprintf(card->longname, "%s at 0x%lx, irq %li",
		card->shortname,
		scard->maestro->io_port, scard->irqptr->irq);

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

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

static int __exit snd_es1968_free2(int dev)
{
	struct snd_es1968 *scard;

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

static int __init alsa_card_es1968_init(void)
{
	int dev, cards;
	struct snd_es1968 *scard;

	for (dev = cards = 0; dev < SND_CARDS; dev++) {
		scard = (struct snd_es1968 *) snd_kcalloc(sizeof(struct snd_es1968), GFP_KERNEL);
		if (!scard)
			continue;
		if (snd_es1968_probe(dev, scard) < 0) {
			snd_kfree(scard);
			break;
		}
		snd_es1968_cards[dev] = scard;
		cards++;
	}
	if (!cards) {
		snd_printk("ESS Maestro soundcard #%i not found or device busy\n", dev + 1);
		return -ENODEV;
	}
	return 0;
}

static void __exit alsa_card_es1968_exit(void)
{
	int dev;

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

module_init(alsa_card_es1968_init)
module_exit(alsa_card_es1968_exit)
