/*
 *  Driver for Mozart (OAK OTI-601) soundcards
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *
 *  Driver supports only one soundcard per system (soundcard limitation).
 *
 *  Driver isn't complete. I don't have documentation and these things
 *  are not working:
 *
 *     1) Line-In input
 *     2) MIDI port
 *
 *   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_OSS_COMPAT__
#define SND_MAIN_OBJECT_FILE
#include "driver.h"
#include "sb.h"
#include "ad1848.h"
#include "initval.h"

#if 0
#define MOZART_DEBUG
#endif
#if 0
#define MOZART_SB_MIDI
#endif
#if 0
#define SND_SKIP_CHECK		/* allow another chips like 82c928 (MAD16) */
#endif

/*

 */

int snd_index = SND_DEFAULT_IDX1;	/* Index 1-MAX */
char *snd_id = SND_DEFAULT_STR1;	/* ID for this card */
int snd_port = SND_DEFAULT_PORT1;	/* 0x530,0xe80,0xf40,0x604 */
int snd_irq = SND_DEFAULT_IRQ1;		/* 7,9,10,11 */
#ifdef MOZART_SB_MIDI
int snd_irq1 = SND_DEFAULT_IRQ1;	/* 5,7,11 */
#endif
int snd_dma1 = SND_DEFAULT_DMA1;	/* 0,1,3 */
int snd_dma1_size = SND_DEFAULT_DMA_SIZE1; /* 8,16,32,64 */
#ifdef MODULE_PARM
MODULE_PARM(snd_index, "i");
MODULE_PARM_DESC(snd_index, "Index value for Mozart soundcard.");
MODULE_PARM(snd_id, "s");
MODULE_PARM_DESC(snd_id, "ID string for Mozart soundcard.");
MODULE_PARM(snd_port, "i");
MODULE_PARM_DESC(snd_port, "Port # for Mozart driver.");
MODULE_PARM(snd_irq, "i");
MODULE_PARM_DESC(snd_irq, "IRQ # for Mozart driver.");
#ifdef MOZART_SB_MIDI
MODULE_PARM(snd_irq1, "i");
MODULE_PARM_DESC(snd_irq1, "Midi IRQ # for Mozart driver.");
#endif
MODULE_PARM(snd_dma1, "i");
MODULE_PARM_DESC(snd_dma1, "DMA1 # for Mozart driver.");
MODULE_PARM(snd_dma1_size, "i");
MODULE_PARM_DESC(snd_dma1_size, "DMA1 size in kB for Mozart driver.");
#endif

/*

 */

#define MOZART_REG		0xf8c
#define MOZART_REG_PASSWORD	0xf8f

/*

 */

static snd_irq_t *snd_irqptr = NULL;
static snd_dma_t *snd_dma1ptr = NULL;
static snd_card_t *snd_card = NULL;
#ifdef MOZART_SB_MIDI
static int snd_sbport = -1;
static int snd_irq1num = SND_IRQ_DISABLE;
static snd_pcm_t *snd_sbpcm = NULL;
#endif
static snd_pcm_t *snd_pcm = NULL;
static snd_kmixer_t *snd_mixer = NULL;
static unsigned short snd_pcm_status_reg = 0;

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

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

static unsigned char snd_mozart_read(int index)
{
	unsigned long flags;
	unsigned char result;

	snd_cli(&flags);
	outb(0xe2, MOZART_REG_PASSWORD);
	result = inb(MOZART_REG + index);
	snd_sti(&flags);
#ifdef MOZART_DEBUG
	snd_printk("mozart_read( %i ) == 0x%x\n", index, result);
#endif
	return result;
}

static void snd_mozart_write(int index, unsigned char val)
{
	unsigned long flags;

#ifdef MOZART_DEBUG
	snd_printk("mozart_write( %i, 0x%x )\n", index, val);
#endif
	snd_cli(&flags);
	outb(0xe2, MOZART_REG_PASSWORD);
	outb(val, MOZART_REG + index);
	snd_sti(&flags);
}

static int snd_mozart_detect(void)
{
	static int possible_ports[] = {0x530, 0xe80, 0xf40, 0x604};
	static int interrupt_bits[] = {-1, -1, -1, -1, -1, -1, -1,
			0x08, -1, 0x10, 0x18, 0x20, -1, -1, -1, -1};
	static int dma_bits[] = {1, 2, 0, 3};
	int tmp, tmp1, portsel;

	if (snd_register_ioport(snd_card, MOZART_REG, 8, "Mozart - control", NULL) < 0)
		return -EBUSY;
	if (snd_port == SND_AUTO_PORT) {
		for (portsel = 0; portsel < 4; portsel++) {
			if (snd_register_ioport(snd_card,
					snd_port = possible_ports[portsel],
					8, "Mozart - WSS", NULL) >= 0) {
				break;
			}
		}
		if (portsel >= 4) {
			snd_unregister_ioports(snd_card);
			return -EBUSY;
		}
	} else {
		for (portsel = 0; portsel < 4; portsel++)
			if (possible_ports[portsel] == snd_port)
				break;
		if (portsel >= 4)
			return -EINVAL;
		if (snd_register_ioport(snd_card, snd_port, 8, "Mozart - WSS", NULL) < 0) {
			snd_unregister_ioports(snd_card);
			return -EBUSY;
		}
	}
	/* try detect OAK chip */
	if ((tmp = snd_mozart_read(1)) == 0xff) {
		snd_printdd("mozart: reg 1 == 0xff\n");
		snd_unregister_ioports(snd_card);
		return -ENODEV;
	}
	if ((tmp1 = inb(MOZART_REG + 1)) == tmp) {
		snd_printdd("mozart: reg 1: tmp1 == tmp == 0x%x\n", tmp);
		snd_unregister_ioports(snd_card);
		return -ENODEV;
	}
	snd_mozart_write(1, tmp ^ 0x80);
	tmp1 = snd_mozart_read(1);
	snd_mozart_write(1, tmp);
	if ((tmp ^ 0x80) != tmp1) {
		snd_printdd("mozart: reg 1: invert failed\n");
		snd_unregister_ioports(snd_card);
		return -ENODEV;
	}
#ifndef SND_SKIP_CHECK
	if ((snd_mozart_read(3) & 0x03) != 0x03) {
		snd_printdd("mozart: reg 3: invalid value == 0x%x\n", snd_mozart_read(3));
		return -ENODEV;
	}
#endif

#if 0
	/* write map... */
	printk("mozart: map [0x00]: ");
	for (tmp = 0; tmp < 8; tmp++) {
		printk("%02x:", snd_mozart_read(tmp));
	}
	printk("\n");
#endif
	/* ok.. OAK chip found.. */
	snd_mozart_write(1, 0x8f | (portsel << 4));	/* enable WSS, disable SB */
	snd_mozart_write(2, 0x03);
#if 0
	snd_mozart_write(4, 0x02);
	snd_mozart_write(5, 0x30);	/* or 0x32? (sync for codec) */
#else
	snd_mozart_write(4, 0x00);
	snd_mozart_write(5, 0x00);
	snd_mozart_write(6, 0x00);
	snd_mozart_write(7, 0x00);
#endif
	/* initialize IRQ for WSS codec */
	tmp = interrupt_bits[snd_irqptr->irq];
	if (tmp < 0)
		return -EINVAL;
	outb(tmp | 0x40, snd_port);
	tmp1 = dma_bits[snd_dma1ptr->dma];
	outb(tmp | tmp1, snd_port);
#ifdef MOZART_SB_MIDI
	/* initialize SB (if needed for midi) */
	if (snd_irq1num != SND_IRQ_DISABLE) {
		snd_sbport = portsel & 2 ? snd_sbport = 0x240 : 0x220;
		tmp = 0;
		switch (snd_card->irqs[snd_irq1num]->irq) {
		case 5:
			tmp = 0x80 | 0x30 | 0x04;
			break;
		case 7:
			tmp = 0x00 | 0x30 | 0x04;
			break;
		case 11:
			tmp = 0x40 | 0x30 | 0x04;
			break;
		default:
			return -EINVAL;
		}
		snd_mozart_write(3, tmp);
		if (snd_register_ioport(snd_card, snd_sbport, 16, "Mozart - SB") < 0) {
			snd_unregister_ioports(snd_card);
			return -EBUSY;
		}
	} else {
		snd_mozart_write(3, 0x03);	/* default settings */
	}
#else
	snd_mozart_write(3, 0x03);	/* default settings */
#endif
#if 0
	snd_mozart_write(1, 0x0f);
	snd_mozart_write(2, 0x03);
	snd_mozart_write(3, 0x03);
	snd_mozart_write(4, 0x00);
	snd_mozart_write(5, 0x00);
	snd_mozart_write(6, 0x00);
	snd_mozart_write(7, 0x00);
#endif
#if 0
	/* write map... */
	printk("mozart: map [0x00]: ");
	for (tmp = 0; tmp < 8; tmp++) {
		printk("%02x:", snd_mozart_read(tmp));
	}
	printk("\n");
#endif
	return 0;
}

static void snd_mozart_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	int loop;
	unsigned char status;

	do {
		loop = 0;
		if ((status = inb(snd_pcm_status_reg)) & 0x01) {
			snd_ad1848_interrupt(snd_pcm, status);
			loop++;
		}
	} while (loop);
}

#ifdef MOZART_SB_MIDI
static void snd_mozart_interrupt1(int irq, void *dev_id, struct pt_regs *regs)
{
}

#endif

static int snd_mozart_resources(void)
{
	static int possible_irqs[] = {9, 10, 11, 7, -1};
#ifdef MOZART_SB_MIDI
	static int possible_irqs1[] = {11, 5, 7, -1};
#endif
	static int possible_dmas[] = {1, 3, 0, -1};
	int err;

	if ((err = snd_register_interrupt(snd_card, "Mozart",
				snd_irq, SND_IRQ_TYPE_ISA,
				snd_mozart_interrupt, NULL,
				possible_irqs, &snd_irqptr)) < 0)
		return err;
#ifdef MOZART_SB_MIDI
	if ((snd_irq1 >= 5 && snd_irq1 <= 11) || snd_irq1 == SND_AUTO_IRQ) {
		if ((err = snd_register_interrupt(snd_card,
				"Mozart", snd_irq1, SND_IRQ_TYPE_ISA,
				snd_mozart_interrupt1, NULL,
				possible_irqs1, &snd_irq1ptr)) < 0)
			return err;
	} else {
		snd_irq1ptr = NULL;
	}
#endif
	if ((err = snd_register_dma_channel(snd_card, "Mozart",
			snd_dma1, SND_DMA_TYPE_ISA,
			snd_dma1_size, possible_dmas, &snd_dma1ptr)) < 0)
		return err;
	return 0;
}

static int snd_mozart_mixer(snd_kmixer_t * mixer)
{
#if 0
	snd_kmixer_channel_t *channel1;

	channel1 = snd_mixer_find_channel(mixer, SND_MIXER_PRI_AUXA);
	channel1->hw.priority = SND_MIXER_PRI_CD;
	channel1->hw.ossdev = SND_MIXER_OSS_CD;
	strcpy(channel1->hw.name, SND_MIXER_ID_CD);
	snd_mixer_reorder_channel(mixer, channel1);
#endif
	return 0;
}

static int snd_mozart_probe(void)
{
	int err;

	snd_card = snd_card_new(snd_index, snd_id,
				snd_mozart_use_inc, snd_mozart_use_dec);
	if (!snd_card)
		return -ENOMEM;
	snd_card->type = SND_CARD_TYPE_MOZART;
	if (snd_mozart_resources() < 0) {
		snd_card_free(snd_card);
		return -EBUSY;
	}
	if ((err = snd_mozart_detect()) < 0) {
		snd_card_free(snd_card);
		return err;
	}
#ifdef MOZART_SB_MIDI
	if (snd_irq1num != SND_IRQ_DISABLE) {
		snd_sbpcm = snd_sbdsp_new_device(snd_card, snd_sbport,
						 snd_irq1num, snd_dma1num,
						 SND_DMA_DISABLE, SB_HW_PRO);
		printk("snd_sbpcm = 0x%lx\n", (long) snd_sbpcm);
		if (!snd_sbpcm)
			goto __nodev;
	}
#endif

	snd_pcm_status_reg = snd_port + 4 + 2;
	snd_pcm = snd_ad1848_new_device(snd_card, snd_port + 4,
					snd_irqptr, snd_dma1ptr,
					AD1848_HW_DETECT);
	if (!snd_pcm)
		goto __nodev;

	snd_mixer = snd_ad1848_new_mixer(snd_pcm, 0);
	if (!snd_mixer)
		goto __nodev;
	snd_mozart_mixer(snd_mixer);

	if (snd_mixer_register(snd_mixer, 0) < 0)
		goto __nodev;
	if (snd_pcm_register(snd_pcm, 0) < 0) {
		snd_mixer_unregister(snd_mixer);
		snd_mixer = NULL;
		goto __nodev;
	}

	strcpy(snd_card->abbreviation, "OAK Mozart");
	strcpy(snd_card->shortname, "OAK Mozart");
	sprintf(snd_card->longname, "OAK Mozart at 0x%x, irq %i, dma %i",
		snd_port,
		snd_irqptr->irq,
		snd_dma1ptr->dma);

	if (!snd_card_register(snd_card))
		return 0;

	return -ENOMEM;

      __nodev:
	if (snd_pcm)
		snd_pcm_free(snd_pcm);
#ifdef MOZART_SB_MIDI
	if (snd_sbpcm)
		snd_pcm_free(snd_sbpcm);
#endif
	snd_card_free(snd_card);
	return -ENXIO;
}

int init_module(void)
{
	int err;

	if ((err = snd_mozart_probe()) < 0) {
		snd_printk("Mozart soundcard not found or device busy\n");
		return err;
	}
	return 0;
}

void cleanup_module(void)
{
	if (!snd_card)
		return;
	snd_card_unregister(snd_card);
	if (snd_mixer)
		snd_mixer_unregister(snd_mixer);
	if (snd_pcm)
		snd_pcm_unregister(snd_pcm);
#ifdef MOZART_SB_MIDI
	if (snd_sbpcm)
		snd_pcm_free(snd_sbpcm);
#endif
	snd_card_free(snd_card);
}
