/*
 *   ALSA driver for Intel ICH (i8x0) chipsets
 *
 *	Copyright (c) 2000 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/pcm.h"
#include "../include/hwdep.h"
#include "../include/intel8x0.h"
#include "../include/info.h"
#include "../include/initval.h"

MODULE_DESCRIPTION("\
Driver: Intel 82801AA,82801BA,82901AB,i810,i815,i820,i830,i840,MX440\n\
PCI: 0x8086=0x2415\n\
PCI: 0x8086=0x2425\n\
PCI: 0x8086=0x2445\n\
PCI: 0x8086=0x7195\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_pbk_frame_size[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 128};
int snd_cap_frame_size[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 128};
int snd_mic_frame_size[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 128};
MODULE_PARM(snd_index, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_index, "Index value for Intel i8x0 soundcard.");
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SND_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for Intel i8x0 soundcard.");
MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
MODULE_PARM(snd_pbk_frame_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_pbk_frame_size, "Playback frame size in kB.");
MODULE_PARM(snd_cap_frame_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_cap_frame_size, "Capture frame size in kB.");
MODULE_PARM(snd_mic_frame_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_mic_frame_size, "MIC capture frame size in kB.");

typedef struct snd_intel8x0_card {
	struct pci_dev *pci;
	snd_irq_t *irqptr;
	snd_dma_t *dma_pbk;	/* playback */
	snd_dma_t *dma_cap;	/* capture */
	snd_dma_t *dma_mic;	/* MIC capture */
	snd_card_t *card;
	intel8x0_t *codec;
	snd_pcm_t *pcm;
	snd_pcm_t *pcm_mic;
	snd_kmixer_t *mixer;
} intel8x0_card_t;

static intel8x0_card_t *snd_intel8x0_cards[SND_CARDS] = SND_DEFAULT_PTR;

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

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

static int __init snd_intel8x0_detect(snd_card_t * card, intel8x0_card_t *scard,
				      unsigned short vendor, unsigned short device)
{
	if ((scard->pci = pci_find_device(vendor, device, scard->pci)) == NULL)
		return -ENODEV;
	if (snd_register_ioport(card, pci_resource_start(scard->pci, 0), 256, "Intel ICH - AC'97", NULL) < 0)
		goto __nodev;
	if (snd_register_ioport(card, pci_resource_start(scard->pci, 1), 64, "Intel ICH - Controller", NULL) < 0)
		goto __nodev;
	return 0;
      __nodev:
	snd_unregister_ioports(card);
	return -ENODEV;
}

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

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

static int __init snd_intel8x0_resources(snd_card_t * card,
					 intel8x0_card_t *scard,
					 int dev)
{
	int err;

	if ((err = snd_register_interrupt(card,
			"Intel ICH", scard->pci->irq,
			SND_IRQ_TYPE_PCI, snd_intel8x0_card_interrupt,
			scard, NULL, &scard->irqptr)) < 0)
		return err;
	if ((err = snd_register_dma_channel(card,
			"Intel ICH - playback", 0,
			SND_DMA_TYPE_PCI, snd_pbk_frame_size[dev],
			NULL, &scard->dma_pbk)) < 0)
		return err;
	if ((err = snd_register_dma_channel(card,
			"Intel ICH - capture", 1,
			SND_DMA_TYPE_PCI, snd_cap_frame_size[dev],
			NULL, &scard->dma_cap)) < 0)
		return err;
	if ((err = snd_register_dma_channel(card,
			"Intel ICH - MIC capture", 2,
			SND_DMA_TYPE_PCI, snd_mic_frame_size[dev],
			NULL, &scard->dma_mic)) < 0)
		return err;
	return 0;
}

static int __init snd_intel8x0_probe(int dev, intel8x0_card_t *scard)
{
	snd_card_t *card;
	snd_pcm_t *pcm = NULL, *pcm_mic = NULL;
	snd_kmixer_t *mixer = NULL;
	intel8x0_t *codec;
	int pcm_dev = 0, mixer_dev = 0;

	card = snd_card_new(snd_index[dev], snd_id[dev],
			    snd_intel8x0_use_inc, snd_intel8x0_use_dec);
	if (card == NULL)
		return -ENOMEM;
	card->type = SND_CARD_TYPE_INTEL8X0;
	do {
		if (!snd_intel8x0_detect(card, scard, PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801))
			break;
	} while (scard->pci);
	if (scard->pci != NULL)
		goto __found;
	do {
		if (!snd_intel8x0_detect(card, scard, PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA))
			break;
	} while (scard->pci);
	if (scard->pci != NULL)
		goto __found;
	do {
		if (!snd_intel8x0_detect(card, scard, PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82901))
			break;
	} while (scard->pci);
	if (scard->pci != NULL)
		goto __found;
	do {
		if (!snd_intel8x0_detect(card, scard, PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_440MX))
			break;
	} while (scard->pci);
	if (scard->pci != NULL)
		goto __found;
	snd_card_free(card);
	return -ENODEV;

      __found:
	if (pci_enable_device(scard->pci)) {
		snd_card_free(card);
		return -ENODEV;
	}
	if (snd_intel8x0_resources(card, scard, dev) < 0) {
		snd_card_free(card);
		return -ENODEV;
	}

	if (snd_intel8x0_create(card, scard->pci,
			       scard->dma_pbk,
			       scard->dma_cap,
			       scard->dma_mic,
			       scard->irqptr,
			       &scard->codec) < 0)
		goto __nodev;
	codec = scard->codec;

	if (snd_intel8x0_mixer(codec, mixer_dev++, 1, &pcm_dev, &mixer) < 0)
		goto __nodev;
	if (snd_intel8x0_pcm(codec, pcm_dev++, &pcm) < 0)
		goto __nodev;
	if (codec->ac97_ext_id & 0x0008) {	/* MIC VRM */
		if (snd_intel8x0_pcm_mic(codec, pcm_dev++, &pcm_mic) < 0)
			goto __nodev;
	}
	
	strcpy(card->abbreviation, "ICH");
	strcpy(card->shortname, "Intel ICH");
	
	switch (codec->pci->device) {
	case PCI_DEVICE_ID_INTEL_82801:
		strcpy(card->shortname, "Intel ICH 82801AA");
		break;
	case PCI_DEVICE_ID_INTEL_82801BA:
		strcpy(card->shortname, "Intel ICH 82801BA");
		break;
	case PCI_DEVICE_ID_INTEL_82901:
		strcpy(card->shortname, "Intel ICH 82901AB");
		break;
	case PCI_DEVICE_ID_INTEL_440MX:
		strcpy(card->shortname, "Intel 440MX");
		break;
	}

	sprintf(card->longname, "%s at 0x%lx, irq %li",
		card->shortname, (long unsigned int)codec->port, scard->irqptr->irq);

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

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

#ifdef MODULE

static int __exit snd_intel8x0_card_free(int dev)
{
	intel8x0_card_t *scard;

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

#endif

static int __init alsa_card_intel8x0_init(void)
{
	int dev, cards;
	intel8x0_card_t *scard;

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

static void __exit alsa_card_intel8x0_exit(void)
{
	int dev;

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

module_init(alsa_card_intel8x0_init)
module_exit(alsa_card_intel8x0_exit)
