
/*
    card-als100.c - driver for Avance Logic ALS100 based soundcards.
    Copyright (C) 1999 by Massimo Piccioni <piccio@caronte.csr.unibo.it>

    Thanks to Pierfrancesco 'qM2' Passerini.

    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/initval.h"
#include "../include/mpu401.h"
#include "../include/opl3.h"
#include "../include/sb.h"

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_port[SND_CARDS] = SND_DEFAULT_PORT;		/* PnP setup */
int snd_mpu_port[SND_CARDS] = SND_DEFAULT_PORT;		/* PnP setup */
int snd_fm_port[SND_CARDS] = SND_DEFAULT_PORT;		/* PnP setup */
int snd_irq[SND_CARDS] = SND_DEFAULT_IRQ;		/* PnP setup */
int snd_mpu_irq[SND_CARDS] = SND_DEFAULT_IRQ;		/* PnP setup */
int snd_dma8[SND_CARDS] = SND_DEFAULT_DMA;		/* PnP setup */
int snd_dma8_size[SND_CARDS] = SND_DEFAULT_DMA_SIZE;	/* 8,16,32,64,128 */
int snd_dma16[SND_CARDS] = SND_DEFAULT_DMA;		/* PnP setup */
int snd_dma16_size[SND_CARDS] = SND_DEFAULT_DMA_SIZE;	/* 8,16,32,64,128 */

#ifdef MODULE_PARM
MODULE_PARM(snd_index, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_index, "Index value for als100 based soundcard.");
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SND_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for als100 based soundcard.");
MODULE_PARM(snd_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_port, "Port # for als100 driver.");
MODULE_PARM(snd_mpu_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_mpu_port, "MPU-401 port # for als100 driver.");
MODULE_PARM(snd_fm_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_fm_port, "FM port # for als100 driver.");
MODULE_PARM(snd_irq, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_irq, "IRQ # for als100 driver.");
MODULE_PARM(snd_mpu_irq, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_mpu_irq, "MPU-401 IRQ # for als100 driver.");
MODULE_PARM(snd_dma8, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma8, "8-bit DMA # for als100 driver.");
MODULE_PARM(snd_dma16, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma16, "16-bit DMA # for als100 driver.");
MODULE_PARM(snd_dma8_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma8_size, "Size of 8-bit DMA # for als100 driver.");
MODULE_PARM(snd_dma16_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma16_size, "Size of 16-bit DMA # for als100 driver.");
#endif	/* MODULE_PARM */

static struct snd_card_als100 {
	snd_irq_t *irqptr;
	snd_irq_t *mpuirqptr;
	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_hwdep_t *synth;
#ifdef CONFIG_ISAPNP
	struct isapnp_dev *dev;
	struct isapnp_dev *devmpu;
	struct isapnp_dev *devopl;
#endif	/* CONFIG_ISAPNP */
} *snd_card_als100_cards[SND_CARDS] = SND_DEFAULT_PTR;

#ifdef CONFIG_ISAPNP
static unsigned int snd_card_als100_pnpids[] = {
	/* ALS100 - PRO16PNP */
	(ISAPNP_VENDOR('A','L','S')<<16)|ISAPNP_DEVICE(0x0001),    /* device */
	(ISAPNP_VENDOR('@','@','@')<<16)|ISAPNP_FUNCTION(0x0001),  /* audio */
	(ISAPNP_VENDOR('@','X','@')<<16)|ISAPNP_FUNCTION(0x0001),  /* mpu401 */
	(ISAPNP_VENDOR('@','H','@')<<16)|ISAPNP_FUNCTION(0x0001),  /* opl */
	/* ALS120 */
	(ISAPNP_VENDOR('A','L','S')<<16)|ISAPNP_DEVICE(0x0120),    /* device */
	(ISAPNP_VENDOR('@','@','@')<<16)|ISAPNP_FUNCTION(0x2001),  /* audio */
	(ISAPNP_VENDOR('@','X','@')<<16)|ISAPNP_FUNCTION(0x2001),  /* mpu401 */
	(ISAPNP_VENDOR('@','H','@')<<16)|ISAPNP_FUNCTION(0x2001),  /* opl */
	0       /* end */
};
#endif	/* CONFIG_ISAPNP */

#define DRIVER_NAME	"snd-card-als100"

#define snd_als100_printk(args...)	snd_printk(__FILE__": " ##args)


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

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

static void snd_card_als100_interrupt(int irq, void *dev_id,
							struct pt_regs *regs)
{
	struct snd_card_als100 *acard;
	snd_pcm_t *pcm;
	sbdsp_t *sb16dsp;
	unsigned long flags;
	unsigned short status;

	if (!(acard = (struct snd_card_als100 *) dev_id))
		return;

	if (!(pcm = acard->pcm))
		return;

	if (!(sb16dsp = (sbdsp_t *) pcm->private_data))
		return;

	spin_lock_irqsave(&sb16dsp->mixer.lock, flags);
	status = snd_sb16mixer_read(&sb16dsp->mixer, 0x82);
	spin_unlock_irqrestore(&sb16dsp->mixer.lock, flags);

	if (status & 0x03)
		snd_sb16dsp_interrupt(pcm, status);
}

static void snd_card_als100_mpu_interrupt(int irq, void *dev_id,
							struct pt_regs *regs)
{
	struct snd_card_als100 *acard;

	if (!(acard = (struct snd_card_als100 *) dev_id))
		return;

	snd_mpu401_uart_interrupt(acard->rmidi);
}

static int snd_card_als100_resources(int dev, struct snd_card_als100 *acard,
							snd_card_t *card)
{
	int error;
	static int no_alternatives[] = {-1};

	if (snd_register_ioport(card, snd_mpu_port[dev], 2,
			DRIVER_NAME" - MPU-401", NULL) < 0)
		snd_mpu_port[dev] = -1;
	else if (snd_register_interrupt(card,
			DRIVER_NAME" - MPU-401", snd_mpu_irq[dev],
			SND_IRQ_TYPE_ISA, snd_card_als100_mpu_interrupt,
			acard, no_alternatives, &acard->mpuirqptr) < 0) {
		snd_unregister_ioports(card);
		snd_mpu_port[dev] = -1;
	}

	if ((error = snd_register_ioport(card, snd_port[dev], 16,
			DRIVER_NAME" - DSP", NULL)) < 0)
		return error;
	if ((error = snd_register_interrupt(card,
			DRIVER_NAME" - DSP", snd_irq[dev],
			SND_IRQ_TYPE_ISA, snd_card_als100_interrupt,
			acard, no_alternatives, &acard->irqptr)) < 0)
		return error;
	if ((error = snd_register_dma_channel(card,
			DRIVER_NAME" - DSP", snd_dma8[dev],
			SND_DMA_TYPE_ISA, snd_dma8_size[dev],
			no_alternatives, &acard->dma8ptr)) < 0)
		return error;
	if ((error = snd_register_dma_channel(card,
			DRIVER_NAME" - DSP", snd_dma16[dev],
			SND_DMA_TYPE_ISA, snd_dma16_size[dev],
			no_alternatives, &acard->dma16ptr)) < 0)
		return error;
	
	if (snd_register_ioport(card, snd_fm_port[dev], 4,
			DRIVER_NAME" - OPL", NULL) < 0)
		snd_fm_port[dev] = -1;

	return 0;
}

#ifdef CONFIG_ISAPNP
static int snd_card_als100_isapnp(int dev, struct snd_card_als100 *acard)
{
	static int idx = 0;
	static struct isapnp_card *card = NULL;
        unsigned int tmp;
	struct isapnp_dev *pdev;

      __again:
	while ((tmp = snd_card_als100_pnpids[idx]) != 0) {
		if ((card = isapnp_find_card(tmp >> 16, tmp & 0xffff, card)))
			break;
		idx += 4;
	}
	if (!card)
		return -ENODEV;

	tmp = snd_card_als100_pnpids[idx+1];
	acard->dev = isapnp_find_dev(card, tmp >> 16, tmp & 0xffff, NULL);
	if (!acard->dev)
		goto __again;
	if (acard->dev->active)
		goto __again;

	tmp = snd_card_als100_pnpids[idx+2];
	acard->devmpu = isapnp_find_dev(card, tmp >> 16, tmp & 0xffff, NULL);
	tmp = snd_card_als100_pnpids[idx+3];
	acard->devopl = isapnp_find_dev(card, tmp >> 16, tmp & 0xffff, NULL);

	pdev = acard->dev;
	if (pdev->prepare(pdev)<0)
		return -EAGAIN;

	if (snd_port[dev] != SND_AUTO_PORT)
		isapnp_resource_change(&pdev->resource[0], snd_port[dev], 16);
	if (snd_dma8[dev] != SND_AUTO_DMA)
		isapnp_resource_change(&pdev->dma_resource[0], snd_dma8[dev], 1);
	if (snd_dma16[dev] != SND_AUTO_DMA)
		isapnp_resource_change(&pdev->dma_resource[1], snd_dma16[dev], 1);
	if (snd_irq[dev] != SND_AUTO_IRQ)
		isapnp_resource_change(&pdev->irq_resource[0], snd_irq[dev], 1);

	if (pdev->activate(pdev)<0) {
		snd_als100_printk("AUDIO isapnp configure failure\n");
		return -EBUSY;
	}

	snd_port[dev] = pdev->resource[0].start;
	snd_dma8[dev] = pdev->dma_resource[1].start;
	snd_dma16[dev] = pdev->dma_resource[0].start;
	snd_irq[dev] = pdev->irq_resource[0].start;

	pdev = acard->devmpu;
	if (!pdev || pdev->prepare(pdev)<0) {
		snd_mpu_port[dev] = -1;
		return 0;
	}

	if (snd_mpu_port[dev] != SND_AUTO_PORT)
		isapnp_resource_change(&pdev->resource[0], snd_mpu_port[dev], 2);
	if (snd_mpu_irq[dev] != SND_AUTO_IRQ)
		isapnp_resource_change(&pdev->irq_resource[0], snd_mpu_irq[dev], 1);

	if (pdev->activate(pdev)<0) {
		snd_als100_printk("MPU-401 isapnp configure failure\n");
		snd_mpu_port[dev] = -1;
		acard->devmpu = NULL;
	} else {
		snd_mpu_port[dev] = pdev->resource[0].start;
		snd_mpu_irq[dev] = pdev->irq_resource[0].start;
	}

	pdev = acard->devopl;
	if (!pdev || pdev->prepare(pdev)<0) {
		snd_fm_port[dev] = -1;
		return 0;
	}

	if (snd_fm_port[dev] != SND_AUTO_PORT)
		isapnp_resource_change(&pdev->resource[0], snd_fm_port[dev], 4);

	if (pdev->activate(pdev)<0) {
		snd_als100_printk("OPL isapnp configure failure\n");
		snd_fm_port[dev] = -1;
		acard->devopl = NULL;
	} else {
		snd_fm_port[dev] = pdev->resource[0].start;
	}

	return 0;
}

static void snd_card_als100_deactivate(struct snd_card_als100 *acard)
{
	if (acard->dev)
		acard->dev->deactivate(acard->dev);
	if (acard->devmpu)
		acard->devmpu->deactivate(acard->devmpu);
	if (acard->devopl)
		acard->devopl->deactivate(acard->devopl);
}
#endif	/* CONFIG_ISAPNP */

static int snd_card_als100_probe(int dev, struct snd_card_als100 *acard)
{
	int error;
	sbdsp_t *codec;
	snd_card_t *card;
	snd_pcm_t *pcm;
	snd_kmixer_t *mixer;
	snd_rawmidi_t *rmidi;
	snd_hwdep_t *synth;

	if (!(card = snd_card_new(snd_index[dev], snd_id[dev],
			snd_card_als100_use_inc, snd_card_als100_use_dec)))
		return -ENOMEM;
	card->type = SND_CARD_TYPE_ALS100;

#ifdef CONFIG_ISAPNP
	if ((error = snd_card_als100_isapnp(dev, acard)))
		goto __on_error;
#else
	snd_als100_printk("you have to enable PnP support ...\n");
	return -ENOSYS;
#endif	/* CONFIG_ISAPNP */

	if ((error = snd_card_als100_resources(dev, acard, card)))
		goto __on_error;

	if (!(pcm = snd_sb16dsp_new_device(card, snd_port[dev], acard->irqptr,
			acard->dma8ptr, acard->dma16ptr, SB_HW_ALS100)))
		goto __on_error;

	codec = (sbdsp_t *) pcm->private_data;
	if (!(mixer = snd_sb16dsp_new_mixer(card,
			&codec->mixer, codec->hardware, 0))) {
		snd_pcm_free(pcm);
		goto __on_error;
	}
	if ((error = snd_mixer_register(mixer, 0 ))) {
		snd_mixer_free(mixer);
		snd_pcm_free(pcm);
		goto __on_error;
	}

	rmidi = NULL;
	if (snd_mpu_port[dev] > 0) {
		if (!(rmidi = snd_mpu401_uart_new_device(card,
				MPU401_HW_ALS100,
				snd_mpu_port[dev], snd_mpu_irq[dev])))
			snd_als100_printk("no MPU-401device at 0x%x ?\n",
				snd_mpu_port[dev]);
		else if (snd_rawmidi_register(rmidi, 0)) {
			snd_rawmidi_free(rmidi);
			rmidi = NULL;
		}
	}

	synth = NULL;
	if (snd_fm_port[dev] > 0) {
		if (!(synth = snd_opl3_new_device(card,
				snd_fm_port[dev], snd_fm_port[dev] + 2,
				OPL3_HW_AUTO, 0)))
			snd_als100_printk("no OPL device at 0x%x-0x%x ?\n",
				snd_fm_port[dev], snd_fm_port[dev] + 2);
		else if (snd_hwdep_register(synth, 0)) {
			snd_hwdep_free(synth);
			synth = NULL;
		}
	}

	if ((error = snd_pcm_register(pcm, 0))) {
		snd_hwdep_unregister(synth);
		snd_rawmidi_unregister(rmidi);
		snd_mixer_unregister(mixer);
		snd_pcm_free(pcm);
		goto __on_error;
	}

	strcpy(card->abbreviation, "ALS100");
	strcpy(card->shortname, "Avance Logic ALS100");
	sprintf(card->longname, "%s soundcard, %s at 0x%x, irq %i, dma %i&%i",
		card->shortname, codec->name, codec->port,
		acard->irqptr->irq, acard->dma8ptr->dma, acard->dma16ptr->dma);
	if (!snd_card_register(card)) {
		acard->card = card;
		acard->pcm = pcm;
		acard->mixer = mixer;
		acard->rmidi = rmidi;
		acard->synth = synth;
		return 0;
	}

	snd_hwdep_unregister(synth);
	snd_rawmidi_unregister(rmidi);
	snd_mixer_unregister(mixer);
	snd_pcm_free(pcm);

      __on_error:
	snd_card_free(card);
	return error ? error : -ENXIO;
}

static void snd_card_als100_free(int dev)
{
	struct snd_card_als100 *acard;

	if ((acard = snd_card_als100_cards[dev])) {
		snd_card_als100_cards[dev] = NULL;
		snd_card_unregister(acard->card);
		if (acard->synth)
			snd_hwdep_unregister(acard->synth);
		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);
#ifdef CONFIG_ISAPNP
		snd_card_als100_deactivate(acard);
#endif	/* CONFIG_ISAPNP */
		snd_kfree(acard);
	}
}

int init_module(void)
{
	int cards, dev;
	struct snd_card_als100 *acard;

	for (dev = cards = 0; dev < SND_CARDS; dev++) {
		acard = (struct snd_card_als100 *) snd_kcalloc(
			sizeof(struct snd_card_als100), GFP_KERNEL);
		if (!acard)
			break;

		if (snd_card_als100_probe(dev, acard) < 0) {
#ifdef CONFIG_ISAPNP
			snd_card_als100_deactivate(acard);
#endif	/* CONFIG_ISAPNP */
			snd_kfree(acard);
			continue;
		}
		snd_card_als100_cards[cards++] = acard;
	}

	if (!cards)
		snd_als100_printk("no ALS100 based soundcards found\n");
	return cards ? 0 : -ENODEV;
}

void cleanup_module(void)
{
	int dev;

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

