#define _PAS2_CARD_C_
#define SND_SA_INTERRUPT
/* linux/kernel/chr_drv/sound/pas2_card.c

Detection routine for the Pro Audio Spectrum cards.

(C) 1992  Hannu Savolainen (hsavolai@cs.helsinki.fi) 
	  Craig Metz (cmetz@thor.tjhsst.edu) */

#include <linux/types.h>
#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/fcntl.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/tty.h>
#include <linux/ctype.h>
#include <asm/io.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <sys/kd.h>
#include <linux/wait.h>
#include "sound_calls.h"
#include "sound_config.h"
#include <linux/soundcard.h>

#if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_PAS)

#define DEFINE_TRANSLATIONS
#include "pas.h"

#include "dev_table.h"

#define DEB(WHAT)	/* (WHAT) */
#define DEB1(WHAT)	/* (WHAT) */

#define DISABLE_INTR(flags)	__asm__ __volatile__("pushfl ; popl %0 ; cli":"=r" (flags));
#define ENABLE_INTR(flags)	__asm__ __volatile__("pushfl ; popl %0 ; sti":"=r" (flags));
#define RESTORE_INTR(flags)	__asm__ __volatile__("pushl %0 ; popfl": :"r" (flags));

/* The Address Translation code is used to convert I/O register addresses to
   be relative to the given base -register */

int translat_code;
static int pas_intr_mask = 0;

static char	pas_model;
static char   *pas_model_names[] = { "", "Pro AudioSpectrum+", "CDPC", "Pro AudioSpectrum 16" };

/* pas_read() and pas_write() are equivalents of inb() and outb() */
/* These routines perform the I/O address translation required */
/* to support other than the default base address */

unsigned char pas_read(int ioaddr)
{
	return inb(ioaddr ^ translat_code);
}

void pas_write(unsigned char data, int ioaddr)
{
	outb(data, ioaddr ^ translat_code);
}

void pas2_msg(char *foo)
{
	printk("    PAS2: %s.\n", foo);
}

/******************* Begin of the Interrupt Handler ********************/

static void pas_interrupt(int unused)
{
	int status;

	status = pas_read(INTERRUPT_STATUS);

	if (status & I_S_PCM_SAMPLE_BUFFER_IRQ) {
#ifndef EXCLUDE_AUDIO
		pas_pcm_interrupt(status, 1);
#endif
		status &= ~I_S_PCM_SAMPLE_BUFFER_IRQ;
	}
	if (status & I_S_MIDI_IRQ) {
#ifndef EXCLUDE_MIDI
		pas_midi_interrupt();
#endif
		status &= ~I_S_MIDI_IRQ;
	}

	pas_write(status, INTERRUPT_STATUS);	/* Clear interrupt */
}

static int set_pas_irq(int interrupt_level)
{
	int retcode;
	struct sigaction sa;
	pas_write(0xff, INTERRUPT_STATUS);	/* Reset pending interrupts */

	sa.sa_handler = pas_interrupt;

#ifdef SND_SA_INTERRUPT
	sa.sa_flags = SA_INTERRUPT;
#else
	sa.sa_flags = 0;
#endif

	sa.sa_mask = 0;
	sa.sa_restorer = NULL;

	retcode = irqaction(interrupt_level, &sa);

	if (retcode < 0) {
		printk("ProAudioSpectrum: IRQ%d already in use\n", interrupt_level);
	}
	return retcode;
}

int pas_set_intr(int mask)
{
	int err;
	if (!mask)
		return 0;

	if (!pas_intr_mask) {
		if ((err = set_pas_irq(PAS_IRQ)) < 0)
			return err;
	}
	pas_intr_mask |= mask;

	pas_write(pas_intr_mask, INTERRUPT_MASK);
	return 0;
}

int pas_remove_intr(int mask)
{
	if (!mask)
		return 0;

	pas_intr_mask &= ~mask;
	pas_write(pas_intr_mask, INTERRUPT_MASK);

	if (!pas_intr_mask) {
		free_irq(PAS_IRQ);
	}
	return 0;
}
/******************* End of the Interrupt handler **********************/

/******************* Begin of the Initialization Code ******************/

int config_pas_hw(void)	
{
	char ok = 1;
	
	pas_write(0x00, INTERRUPT_MASK);

	pas_write(0x36, SAMPLE_COUNTER_CONTROL);	/* Local timer control register */

	pas_write(0x36, SAMPLE_RATE_TIMER);	/* Sample rate timer (16 bit) */
	pas_write(0, SAMPLE_RATE_TIMER);

	pas_write(0x74, SAMPLE_COUNTER_CONTROL);	/* Local timer control register */

	pas_write(0x74, SAMPLE_BUFFER_COUNTER);	/* Sample count register (16 bit) */
	pas_write(0, SAMPLE_BUFFER_COUNTER);

	pas_write(F_F_PCM_BUFFER_COUNTER | F_F_PCM_RATE_COUNTER | F_F_MIXER_UNMUTE | 1, FILTER_FREQUENCY);	
	pas_write(P_C_PCM_DMA_ENABLE | P_C_PCM_MONO | P_C_PCM_DAC_MODE | P_C_MIXER_CROSS_L_TO_L | P_C_MIXER_CROSS_R_TO_R, PCM_CONTROL);
	pas_write(S_M_PCM_RESET | S_M_FM_RESET | S_M_SB_RESET | S_M_MIXER_RESET, SERIAL_MIXER);

	pas_write(I_C_1_BOOT_RESET_ENABLE, IO_CONFIGURATION_1);
	pas_write(I_C_2_PCM_DMA_translate[PAS_DMA], IO_CONFIGURATION_2);
	pas_write(I_C_3_PCM_IRQ_translate[PAS_IRQ], IO_CONFIGURATION_3);
	
	if (!I_C_2_PCM_DMA_translate[PAS_DMA]) {
		pas2_msg("Invalid DMA selection");
		ok = 0;
	}
 	if (!I_C_3_PCM_IRQ_translate[PAS_IRQ]) {
		pas2_msg("Invalid IRQ selection");
		ok = 0;
	}

#ifdef BROKEN_BUS_CLOCK 
	pas_write(S_C_1_PCS_ENABLE | S_C_1_PCS_STEREO | S_C_1_PCS_REALSOUND | S_C_1_FM_EMULATE_CLOCK, SYSTEM_CONFIGURATION_1);
#else
	pas_write(S_C_1_PCS_ENABLE | S_C_1_PCS_STEREO | S_C_1_PCS_REALSOUND, SYSTEM_CONFIGURATION_1);     
#endif
/*	pas_write(S_C_2_PCM_16_BIT, SYSTEM_CONFIGURATION_2);	Don't do this	*/
	pas_write(0x18, SYSTEM_CONFIGURATION_3);	/* ??? */ 

	pas_write(F_F_MIXER_UNMUTE | 0x01, FILTER_FREQUENCY);	/* Sets mute off and selects filter rate of 17.897 kHz */

	if (pas_model == PAS_16) 
		pas_write(8, PRESCALE_DIVIDER);
	else
		pas_write(0, PRESCALE_DIVIDER);

	pas_write(P_M_MV508_ADDRESS | 5, PARALLEL_MIXER);
	pas_write(5, PARALLEL_MIXER);
		
 /* Turn on Sound Blaster compatibility */
 /* bit 1 = SB emulation */
 /* bit 0 = MPU401 emulation (CDPC only :-( ) */
 /*	pas_write(0x02, COMPATIBILITY_ENABLE);	Currently off */

 /* "Emulation address"
	pas_write((SBC_IO_BASE >> 4) & 0x0f, EMULATION_ADDRESS); 	Currently not set */

	if (!ok) 
		pas2_msg("Driver not enabled");

	return ok;
}

int detect_pas_hw(void)
{
	unsigned char board_id, foo;

/*	WARNING: Setting an option like W:1 or so that disables warm boot reset of the card will screw up
		this detect code something fierce. Adding code to handle this means possibly interfering with
		other cards on the bus if you have something on base port 0x388. SO be forewarned. */

	outb(0xBC, MASTER_DECODE);							/* Talk to first board */
	outb(PAS_BASE >> 2, MASTER_DECODE);						/* Set base address */
	translat_code = PAS_DEFAULT_BASE ^ PAS_BASE;
	pas_write(1, WAIT_STATE);	/* One wait-state */

	board_id = pas_read(INTERRUPT_MASK);

	if (board_id == 0xff)
		return 0;

/*	We probably have a PAS-series board, now check for a PAS2-series board by trying to change the board 
	revision bits. PAS2-series hardware won't let you do this - the bits are read-only. */

	foo = board_id ^ 0xe0;

	pas_write(foo, INTERRUPT_MASK);
	foo = inb(INTERRUPT_MASK);
	pas_write(board_id, INTERRUPT_MASK);

	if (board_id != foo)	/* Not a PAS2 */
		return 0;

	if ((pas_model = O_M_1_to_card[pas_read(OPERATION_MODE_1) & 0x0f])) 
	{
		printk("   PAS2: Found a %s board, revision %d.\n", pas_model_names[(int)pas_model], pas_read(BOARD_REV_ID));
		printk("         IRQ = %d, DMA = %d\n", PAS_IRQ, PAS_DMA);
	}

	return pas_model;
}

long detect_pas_card(long mem_start)
{
	if (detect_pas_hw()) {
		if (config_pas_hw()) {

#ifndef EXCLUDE_AUDIO
			mem_start = pas_pcm_init(mem_start);
#endif

#ifndef EXCLUDE_MIDI
			mem_start = pas_midi_init(mem_start);
#endif

#ifndef EXCLUDE_YM3812
			if (ym3812_detect(FM_MONO))
				mem_start = ym3812_init(mem_start);
			else
				pas2_msg("FM-Synthesizer cannot be initialized");
#endif

			pas_init_mixer();
		}
	}

	return mem_start;
}

#endif
