/*
 *  The driver for the Yamaha's DS1/DS1E cards
 *  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/ymfpci.h"
#include "../include/mpu401.h"
#include "../include/opl3.h"
#include "../include/initval.h"

MODULE_DESCRIPTION("\
Driver: Yamaha DS-XG PCI\n\
Card: YMF724\n\
Card: YMF724F\n\
Card: YMF740\n\
Card: YMF740C\n\
Card: YMF744\n\
Card: YMF754\n\
PCI: 0x1073=0x0004\n\
PCI: 0x1073=0x000d\n\
PCI: 0x1073=0x000a\n\
PCI: 0x1073=0x000c\n\
PCI: 0x1073=0x0010\n\
PCI: 0x1073=0x0012\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_enable[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 1}; /* all enabled */
int snd_dac_frame_size[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 128};
int snd_adc_frame_size[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 128};
int snd_ac97_frame_size[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 128};
int snd_fm_port[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = -1};
int snd_mpu_port[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = -1};
int snd_mpu_irq[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 the Yamaha DS-XG PCI soundcard.");
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SND_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for the Yamaha DS-XG PCI soundcard.");
MODULE_PARM(snd_enable, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_enable, "Enable Yamaha DS-XG soundcard. [BOOL]");
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 Yamaha DS-XG 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 Yamaha DS-XG soundcard.");
MODULE_PARM(snd_ac97_frame_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_ac97_frame_size, "Direct AC'97 frame size in kB for the Yamaha DS-XG soundcard.");
MODULE_PARM(snd_mpu_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_mpu_port, "MPU-401 port. [list=0x330,0x320,0x310,0x300]");
MODULE_PARM(snd_mpu_irq, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_mpu_irq, "MPU-401 IRQ number [list=5,7,0,10,11].");
MODULE_PARM(snd_fm_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_fm_port, "FM port. [list=0x388,0x3c8,0x3e0,0x3e8]");

struct snd_ymfpci {
	struct pci_dev *pci;
	snd_irq_t *irqptr;
	snd_irq_t *irqmpuptr;
	snd_dma_t *dma1ptr;	/* DAC frame */
	snd_dma_t *dma2ptr;	/* REC frame */
	snd_dma_t *dma3ptr;	/* AC'97 frame */
	snd_card_t *card;
	ymfpci_t *codec;
	snd_pcm_t *pcm;
	snd_kmixer_t *mixer;
	snd_rawmidi_t *rmidi;
	snd_hwdep_t *synth;
	u16 old_legacy_ctrl;
};

static struct pci_device_id snd_ymfpci_ids[] __devinitdata = {
        { 0x1073, 0x0004, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, },   /* YMF724 */
        { 0x1073, 0x000d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, },   /* YMF724F */
        { 0x1073, 0x000a, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, },   /* YMF740 */
        { 0x1073, 0x000c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, },   /* YMF740C */
        { 0x1073, 0x0010, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, },   /* YMF744 */
        { 0x1073, 0x0012, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, },   /* YMF754 */
	{ 0, }
};

MODULE_DEVICE_TABLE(pci, snd_ymfpci_ids);

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

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

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

	if (scard == NULL || scard->codec == NULL)
		return;
	snd_ymfpci_interrupt(scard->codec);
}

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

	if (scard == NULL || scard->rmidi == NULL)
		return;
	snd_mpu401_uart_interrupt(scard->rmidi);
}

static void snd_ymfpci_free(void *private_data)
{
	struct snd_ymfpci *scard = (struct snd_ymfpci *)private_data;
	
	if (scard != NULL) {
		pci_write_config_word(scard->pci, 0x40, scard->old_legacy_ctrl);
		snd_kfree(scard);
	}
}

static int __init snd_card_ymfpci_probe(struct pci_dev *pci,
					const struct pci_device_id *id)
{
	static int dev = 0;
	snd_card_t *card;
	struct snd_ymfpci *scard;
	snd_pcm_t *pcm = NULL;
	snd_kmixer_t *mixer = NULL;
	snd_rawmidi_t *rmidi = NULL;
	snd_hwdep_t *synth = NULL;
	char *str, str1[32];
	int err;
	long tmp;
	u16 legacy_ctrl, legacy_ctrl2;

	for ( ; dev < SND_CARDS; dev++) {
		if (!snd_enable[dev]) {
			dev++;
			return -ENOENT;
		}
		break;
	}
	if (dev >= SND_CARDS)
		return -ENODEV;

	scard = snd_kcalloc(sizeof(struct snd_ymfpci), GFP_KERNEL);
	if (scard == NULL)
		return -ENOMEM;

	card = snd_card_new(snd_index[dev], snd_id[dev],
			    snd_card_ymfpci_use_inc,
			    snd_card_ymfpci_use_dec);
	if (card == NULL) {
		snd_kfree(scard);
		return -ENOMEM;
	}
	card->type = SND_CARD_TYPE_YMFPCI;
	card->private_free = snd_ymfpci_free;
	card->private_data = scard;
	scard->pci = pci;
	if ((err = pci_enable_device(pci)))
		goto __nodev;
	switch (id->device) {
	case 0x0004: str = "YMF724"; break;
	case 0x000d: str = "YMF724F"; break;
	case 0x000a: str = "YMF740"; break;
	case 0x000c: str = "YMF740C"; break;
	case 0x0010: str = "YMF744"; break;
	case 0x0012: str = "YMF754"; break;
	default: str = "???"; break;
	}

	legacy_ctrl = 0;
	legacy_ctrl2 = 0;

	if (id->device >= 0x0010) { /* YMF 744/754 */
		sprintf(str1, "%s legacy", str);
		if (snd_register_ioport(card, pci_resource_start(scard->pci, 1), 64, str1, NULL) < 0)
			goto __nodev;
#if 0	/* joystick driver should do this */
		sprintf(str1, "%s joystick", str);
		if (snd_register_ioport(card, pci_resource_start(scard->pci, 2), 4, str1, NULL) < 0)
			goto __nodev;
#endif
		sprintf(str1, "%s OPL3", str);
		if (snd_fm_port[dev] < 0)
			snd_fm_port[dev] = pci_resource_start(scard->pci, 1);
		else if (snd_register_ioport(card, snd_fm_port[dev], 4, str1, NULL) < 0)
			snd_fm_port[dev] = -1;
		if (snd_fm_port[dev] >= 0) {
			legacy_ctrl |= 2;
			pci_write_config_word(pci, 0x60, snd_fm_port[dev]);
		}
		sprintf(str1, "%s MPU401", str);
		if (snd_mpu_port[dev] < 0)
			snd_mpu_port[dev] = pci_resource_start(scard->pci, 1) + 0x20;
		else if (snd_register_ioport(card, snd_mpu_port[dev], 2, str1, NULL) < 0)
			snd_mpu_port[dev] = -1;
		if (snd_mpu_port[dev] >= 0) {
			legacy_ctrl |= 8;
			pci_write_config_word(pci, 0x62, snd_fm_port[dev]);
		}
#if 0
		sprintf(str1, "%s joystick", str);
		if (snd_joystick_port[dev] < 0)
			snd_joystick_port[dev] = pci_resource_start(scard->pci, 2);
		else if (snd_register_ioport(card, snd_joystick_port[dev], 4, str1, NULL) < 0)
			snd_joystick_port[dev] = -1;
		if (snd_joystick_port[dev] >= 0) {
			legacy_ctrl |= 4;
			pci_write_config_word(pci, 0x66, snd_joystick_port[dev]);
		}
#endif
	} else {
		sprintf(str1, "%s OPL3", str);
		switch (snd_fm_port[dev]) {
		case 0x388: legacy_ctrl2 |= 0; break;
		case 0x398: legacy_ctrl2 |= 1; break;
		case 0x3a0: legacy_ctrl2 |= 2; break;
		case 0x3a8: legacy_ctrl2 |= 3; break;
		default: snd_fm_port[dev] = -1; break;
		}
		if (snd_fm_port[dev] > 0 && snd_register_ioport(card, snd_fm_port[dev], 4, str1, NULL) >= 0)
			legacy_ctrl |= 2;
		else {
			legacy_ctrl2 &= ~3;
			snd_fm_port[dev] = -1;
		}
		sprintf(str1, "%s MPU401", str);
		switch (snd_mpu_port[dev]) {
		case 0x330: legacy_ctrl2 |= 0 << 4; break;
		case 0x300: legacy_ctrl2 |= 1 << 4; break;
		case 0x332: legacy_ctrl2 |= 2 << 4; break;
		case 0x334: legacy_ctrl2 |= 3 << 4; break;
		default: snd_mpu_port[dev] = -1; break;
		}
		if (snd_mpu_port[dev] > 0 && snd_register_ioport(card, snd_mpu_port[dev], 2, str1, NULL) >= 0)
			legacy_ctrl |= 8;
		else {
			legacy_ctrl2 &= ~(3 << 4);
			snd_mpu_port[dev] = -1;
		}
#if 0
		sprintf(str1, "%s joystick", str);
		switch (snd_joystick_port[dev]) {
		case 0x201: legacy_ctrl2 |= 0 << 6; break;
		case 0x202: legacy_ctrl2 |= 1 << 6; break;
		case 0x204: legacy_ctrl2 |= 2 << 6; break;
		case 0x205: legacy_ctrl2 |= 3 << 6; break;
		default: snd_joystick_port[dev] = -1; break;
		}
		if (snd_joystick_port[dev] > 0 && snd_register_ioport(card, snd_joystick_port[dev], 2, str1, NULL) >= 0)
			legacy_ctrl |= 4;
		else {
			legacy_ctrl2 &= ~(3 << 6);
			snd_joystick_port[dev] = -1;
		}
#endif
	}
	pci_read_config_word(pci, 0x40, &scard->old_legacy_ctrl);
	switch (snd_mpu_irq[dev]) {
	case 5:	break;
	case 7:	legacy_ctrl |= 1 << 11; break;
	case 9: legacy_ctrl |= 2 << 11; break;
	case 10: legacy_ctrl |= 3 << 11; break;
	case 11: legacy_ctrl |= 4 << 11; break;
	default: snd_mpu_irq[dev] = -1; break;
	}
	legacy_ctrl |= (snd_mpu_irq[dev] > 0 ? 0x10 : 0);	/* MPU401 IRQ enable */
	snd_printd("legacy_ctrl = 0x%x\n", legacy_ctrl);
	pci_write_config_word(pci, 0x40, legacy_ctrl);
	snd_printd("legacy_ctrl2 = 0x%x\n", legacy_ctrl2);
	pci_write_config_word(pci, 0x42, legacy_ctrl2);
	if ((err = snd_register_interrupt(card,
			str, scard->pci->irq,
			SND_IRQ_TYPE_PCI, snd_card_ymfpci_interrupt,
			scard, NULL, &scard->irqptr)) < 0)
		goto __nodev;
	if (snd_mpu_irq[dev] > 0) {
		tmp = -1;
		sprintf(str1, "%s - MPU401", str);
		if ((err = snd_register_interrupt(card,
				str1, snd_mpu_irq[dev],
				SND_IRQ_TYPE_ISA, snd_card_ymfpci_mpu401_interrupt,
				scard, &tmp, &scard->irqmpuptr)) < 0)
			goto __nodev;
	}
	if ((err = snd_register_dma_channel(card,
			"YMFPCI - DAC frame", 0,
			SND_DMA_TYPE_PCI, snd_dac_frame_size[dev],
			NULL, &scard->dma1ptr)) < 0)
		goto __nodev;
	scard->dma1ptr->multi = 1;
	if ((err = snd_register_dma_channel(card,
			"YMFPCI - ADC frame", 2,
			SND_DMA_TYPE_PCI, snd_adc_frame_size[dev],
			NULL, &scard->dma2ptr)) < 0)
		goto __nodev;
	if ((err = snd_register_dma_channel(card,
			"YMFPCI - AC'97 frame", 3,
			SND_DMA_TYPE_PCI, snd_adc_frame_size[dev],
			NULL, &scard->dma3ptr)) < 0)
		goto __nodev;
	if ((err = snd_ymfpci_create(card, scard->pci,
			      scard->dma1ptr,
			      scard->dma2ptr,
			      scard->dma3ptr,
			      scard->irqptr,
			      &scard->codec)) < 0)
		goto __nodev;
	if ((err = snd_ymfpci_pcm(scard->codec, 0, &pcm)) < 0)
		goto __nodev;
	if ((err = snd_ymfpci_pcm_spdif(scard->codec, 1, &pcm)) < 0)
		goto __nodev;
	if ((err = snd_ymfpci_pcm2(scard->codec, 2, &pcm)) < 0)
		goto __nodev;
	if ((err = snd_ymfpci_mixer(scard->codec, 0, pcm, &mixer)) < 0)
		goto __nodev;
	if (snd_mpu_port[dev] > 0) {
		if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_YMFPCI,
					       snd_mpu_port[dev],
					       scard->irqmpuptr ? scard->irqmpuptr->irq : -1, &rmidi)) < 0)
			goto __nodev;
	}
	if (snd_fm_port[dev] > 0) {
		snd_opl3_new(card, 0,
			     snd_fm_port[dev],
			     snd_fm_port[dev] + 2,
			     OPL3_HW_OPL3, -1, &synth);
	}
	strcpy(card->abbreviation, "YMFPCI");
	sprintf(card->shortname, "Yamaha DS-XG PCI (%s)", str);
	sprintf(card->longname, "%s at 0x%lx, irq %li",
		card->shortname,
		pci_resource_start(scard->pci, 0),
		scard->irqptr->irq);

	if ((err = snd_card_register(card)) >= 0) {
		scard->card = card;
		scard->mixer = mixer;
		scard->pcm = pcm;
		scard->rmidi = rmidi;
		scard->synth = synth;
		PCI_SET_DRIVER_DATA(pci, card);
		dev++;
		return 0;
	}

      __nodev:
	snd_card_free(card);
	return err;
}

static void __exit snd_card_ymfpci_remove(struct pci_dev *pci)
{
	snd_card_free(PCI_GET_DRIVER_DATA(pci));
	PCI_SET_DRIVER_DATA(pci, NULL);
}

static struct pci_driver driver = {
	name: "Yamaha DS-XG PCI",
	id_table: snd_ymfpci_ids,
	probe: snd_card_ymfpci_probe,
	remove: snd_card_ymfpci_remove,
};

static int __init alsa_card_ymfpci_init(void)
{
	int err;

	if ((err = pci_module_init(&driver)) < 0) {
#ifdef MODULE
		snd_printk("Yamaha DS-XG PCI soundcard not found or device busy\n");
#endif
		return err;
	}
	return 0;
}

static void __exit alsa_card_ymfpci_exit(void)
{
	pci_unregister_driver(&driver);
}

module_init(alsa_card_ymfpci_init)
module_exit(alsa_card_ymfpci_exit)
