/*
 *  Driver for HAL2 soundcard
 *  Copyright (c) by Ulf Carlsson <ulfc@thepuffingroup.com>
 *
 *   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 "driver.h"
#include "hal2.h"
#include "initval.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_irq[SND_CARDS] = SND_DEFAULT_IRQ;	/* 3,5,9,11,12,15 */
int snd_dma1_size[SND_CARDS] = SND_DEFAULT_DMA_SIZE;	/* 8,16,32,64,128 */
int snd_dma2_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 HAL2 soundcard.");
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SND_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for HAL2 soundcard.");
MODULE_PARM(snd_irq, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_irq, "IRQ # for HAL2 driver.");
MODULE_PARM(snd_dma1_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma1_size, "DMA1 size in kB for HAL2 driver.");
MODULE_PARM(snd_dma2_size, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_dma2_size, "DMA2 size in kB for HAL2 driver.");
#endif

struct snd_card_hal2 {
	snd_irq_t *irqptr;
	snd_dma_t *dma1ptr;
	snd_dma_t *dma2ptr;
	snd_card_t *card;
	snd_hal2_card_t *hal2;
	snd_pcm_t *pcm;
	snd_kmixer_t *mixer;
	snd_rawmidi_t *midi_uart;
	snd_hal2_ctl_regs_t *ctl_regs;
	snd_hal2_aes_regs_t *aes_regs;
	snd_hal2_vol_regs_t *vol_regs;
	snd_hal2_syn_regs_t *syn_regs;
};

static struct snd_card_hal2 *snd_hal2_cards[SND_CARDS] = SND_DEFAULT_PTR;

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

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

static int snd_card_hal2_detect(snd_hal2_card_t * hal2)
{
	unsigned short board, major, minor;
	unsigned short rev;

	/* reset HAL2 */
	snd_hal2_isr_write(hal2, 0);

	/* release reset */
	snd_hal2_isr_write(hal2, H2_ISR_GLOBAL_RESET_N | H2_ISR_CODEC_RESET_N | H2_ISR_QUAD_MODE);

	snd_hal2_i_write16(hal2, H2I_RELAY_C, H2I_RELAY_C_STATE); 

#define CONFIG_SND_DEBUG_DETECT

#ifdef CONFIG_SND_DEBUG_DETECT
	if ((rev = snd_hal2_rev_look(hal2)) & H2_REV_AUDIO_PRESENT) {
		snd_printk("HAL2 wasn't there? rev: 0x%04hx\n", rev);
		return -ENODEV;
	}
#else
	if ((rev = snd_hal2_rev_look(hal2)) & H2_REV_AUDIO_PRESENT)
		return -ENODEV;
#endif

	board = (rev & H2_REV_BOARD_M) >> 12;
	major = (rev & H2_REV_MAJOR_CHIP_M) >> 4;
	minor = (rev & H2_REV_MINOR_CHIP_M);

	snd_printk("SGI HAL2 Processor, Revision %i.%i.%i\n",
	       board, major, minor);

	if (board != 4 || major != 1 || minor != 0) {
		snd_printk("Other revision than 4.1.0 detected\n");
		snd_printk("Your card is probably not supported\n");
	}

	return 0;
}

static void snd_card_hal2_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	struct snd_card_hal2 *hal2card = (struct snd_card_hal2 *) dev_id;

	if (!hal2card || !hal2card->hal2 || !hal2card->pcm)
		return;

	snd_hal2_interrupt(hal2card->hal2);
}

static int snd_card_hal2_resources(int dev, struct snd_card_hal2 *hal2card,
				   snd_card_t * card)
{
	int err;

	/* This is not really a PCI interrupt, but PCI interrupts are shareable
	 * so this will probably work
	 */
	if ((err = snd_register_interrupt(card, "HAL2",
			12, SND_IRQ_TYPE_PCI,
			snd_card_hal2_interrupt, hal2card,
			NULL, &hal2card->irqptr)) < 0) {
		snd_printk("Couldn't get irq\n");
		return err;
	}

	/* It seems like we can use a the PCI dma type here. It's not entirely
	 * true, but it seems to work.
	 */
	if ((err = snd_register_dma_channel(card, "HAL2 record", 0,
				SND_DMA_TYPE_PCI, snd_dma1_size[dev], NULL,
				&hal2card->dma1ptr)) < 0) {
		snd_printk("Couldn't get dma1\n");
		return err;
	}
	if ((err = snd_register_dma_channel(card, "HAL2 playback", 0,
			SND_DMA_TYPE_PCI, snd_dma2_size[dev], NULL,
			&hal2card->dma2ptr)) < 0) {
		snd_printk("Couldn't get dma2\n");
		return err;
	}
	return 0;
}

static int snd_card_hal2_probe(int dev, struct snd_card_hal2 *hal2card)
{
	snd_card_t *card;
	snd_hal2_card_t *hal2 = NULL;
	snd_pcm_t *pcm = NULL;

	card = snd_card_new(snd_index[dev], snd_id[dev],
			    snd_card_hal2_use_inc, snd_card_hal2_use_dec);
	if (!card)
		return -ENOMEM;
	if (snd_card_hal2_resources(dev, hal2card, card) < 0) {
		snd_card_free(card);
		return -EBUSY;
	}
	hal2 = snd_hal2_new_card(card, hal2card->irqptr, hal2card->dma1ptr,
				 hal2card->dma2ptr,
				 hal2card->ctl_regs,
				 hal2card->aes_regs,
				 hal2card->vol_regs,
				 hal2card->syn_regs);
	if (!hal2) {
		snd_card_free(card);
		return -ENOMEM;
	}

	if (snd_card_hal2_detect(hal2)) {
		snd_card_free(card);
		return -ENODEV;
	}

	pcm = snd_hal2_pcm(hal2);
	if (!pcm)
		goto __nodev;
	
	if (snd_pcm_register(pcm, 0) < 0) {
		goto __nodev;
	}
	strcpy(card->abbreviation, "HAL2");
	strcpy(card->shortname, "HAL2");
	sprintf(card->longname, "HAL2 at irq %i", hal2card->irqptr->irq);

	if (!snd_card_register(card)) {
		hal2card->card = card;
		hal2card->hal2 = hal2;
		hal2card->pcm = pcm;
		return 0;
	}

	snd_pcm_unregister(pcm);
	pcm = NULL;

      __nodev:
	if (pcm)
		snd_pcm_free(pcm);
	if (card)
		snd_card_free(card);
	return -ENXIO;
}

int init_module(void)
{
	int dev = 0;
	struct snd_card_hal2 *hal2card;

#ifndef LINUX_2_1
	register_symtab(NULL);
#endif
	/* I doubt anyone has a machine with two HAL2 cards. It's possible to
	 * have two HPC's, so it is probably possible to have two HAL2 cards.
	 * Show me such a machine and I'll happily implement support for your
	 * multiple HAL2 cards!
	 */
	hal2card = (struct snd_card_hal2 *)
				snd_malloc(sizeof(struct snd_card_hal2));
	if (!hal2card)
		return -ENOMEM;
	memset(hal2card, 0, sizeof(struct snd_card_hal2));

	hal2card->ctl_regs = (snd_hal2_ctl_regs_t *) KSEG1ADDR(H2_CTL_PIO);
	hal2card->aes_regs = (snd_hal2_aes_regs_t *) KSEG1ADDR(H2_AES_PIO);
	hal2card->vol_regs = (snd_hal2_vol_regs_t *) KSEG1ADDR(H2_VOL_PIO);
	hal2card->syn_regs = (snd_hal2_syn_regs_t *) KSEG1ADDR(H2_SYN_PIO);

	if (snd_card_hal2_probe(dev, hal2card) < 0) {
		snd_free(hal2card, sizeof(struct snd_card_hal2));
		snd_printk("HAL2 soundcard #%i not found or device busy\n",
			   dev + 1);
		return -ENODEV;
	}
	snd_hal2_cards[dev] = hal2card;

	return 0;
}

void cleanup_module(void)
{
	int dev = 0;
	struct snd_card_hal2 *hal2card;
	snd_pcm_t *pcm;

	/* We don't have to check all the indexes since we don't have suppor for
	 * more than one card, but let's do it anyway
	 */
	hal2card = snd_hal2_cards[dev];
	if (hal2card) {
		snd_card_unregister(hal2card->card);
		if (hal2card->pcm) {
			pcm = hal2card->pcm;
			hal2card->pcm = NULL;	/* turn off interrupts */
			snd_pcm_unregister(pcm);
		}
		snd_card_free(hal2card->card);
		snd_free(hal2card, sizeof(struct snd_card_hal2));
	}
}
