/*
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *                   Creative Labs, Inc.
 *  Routines for control of EMU10K1 chips
 *
 *  BUGS:
 *    --
 *
 *  TODO:
 *    --
 *
 *   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.
 *
 */

/* CHANGES
 * 31.01.2000	Takashi Iwai
 *	- snd_emu10k1_init_efx() was recoded
 *	- Support for rear volume control
 */

#define SND_MAIN_OBJECT_FILE
#include "../../include/driver.h"
#include "../../include/emu10k1.h"

/*************************************************************************
 * EMU10K1 effect manager
 *************************************************************************/

inline static void snd_emu10k1_write_op(emu10k1_t *emu, int op, int z, int w, int x, int y, u32 *pc)
{
	snd_emu10k1_efx_write(emu, *pc * 2, (x << 10) | y);
	snd_emu10k1_efx_write(emu, *pc * 2 + 1, (op << 20) | (z << 10) | w);
	*pc += 1;
}

static void snd_emu10k1_init_efx(emu10k1_t *emu)
{
	int i;
	u32 pc = 0;

	for (i = 0; i < 512; i++) {
		snd_emu10k1_efx_write(emu, i * 2, 0x10040);
		snd_emu10k1_efx_write(emu, i * 2 + 1, 0x610040);
	}

	for (i = 0; i < 256; i++)
		snd_emu10k1_ptr_write(emu, FXGPREGBASE + i, 0, 0);

	/* FX-8010 DSP Registers:
	   FX Bus
	     0x000-0x00f : 16 registers
	   Input
	     0x010/0x011 : AC97 Codec (l/r)
	     0x012/0x013 : ADC, S/PDIF (l/r)
	     0x014/0x015 : Mic(left), Zoom (l/r)
	     0x016/0x017 : APS S/PDIF?? (l/r)
	   Output
	     0x020/0x021 : AC97 Output (l/r)
	     0x022/0x023 : TOS link out (l/r)
	     0x024/0x025 : ??? (l/r)
	     0x026/0x027 : LiveDrive Headphone (l/r)
	     0x028/0x029 : Rear Channel (l/r)
	     0x02a/0x02b : ADC Recording Buffer (l/r)
	   Constants
	     0x040 - 0x044 = 0 - 4
	     0x045 = 0x8, 0x046 = 0x10, 0x047 = 0x20
	     0x048 = 0x100, 0x049 = 0x10000, 0x04a = 0x80000
	     0x04b = 0x10000000, 0x04c = 0x20000000, 0x04d = 0x40000000
	     0x04e = 0x80000000, 0x04f = 0x7fffffff
	   Temporary Values
	     0x056 : Accumulator
	     0x058 : Noise source?
	     0x059 : Noise source?
	   General Purpose Registers
	     0x100 - 0x1ff
	   Tank Memory Data Registers
	     0x200 - 0x2ff
	   Tank Memory Address Registers
	     0x300 - 0x3ff
	     */

	/* Operators:
	   0 : z := w + (x * y >> 31)
	   4 : z := w + x * y
	   6 : z := w + x + y
	   */

	/* Routing - this will be configurable in later version */

	/* GPR[0/1] = FX * 4 + SPDIF-in */
	snd_emu10k1_write_op(emu, 4, 0x100, 0x12, 0, 0x44, &pc);
	snd_emu10k1_write_op(emu, 4, 0x101, 0x13, 1, 0x44, &pc);
	/* GPR[0/1] += APS-input */
	snd_emu10k1_write_op(emu, 6, 0x100, 0x100, 0x40, emu->APS ? 0x16 : 0x40, &pc);
	snd_emu10k1_write_op(emu, 6, 0x101, 0x101, 0x40, emu->APS ? 0x17 : 0x40, &pc);
	/* FrontOut (AC97) = GPR[0/1] */
	snd_emu10k1_write_op(emu, 6, 0x20, 0x40, 0x40, 0x100, &pc);
	snd_emu10k1_write_op(emu, 6, 0x21, 0x40, 0x40, 0x101, &pc);
	/* RearOut = (GPR[0/1] * RearVolume GPR[10/11]) >> 31 */
	/*   RearVolume = GRP[0x10/0x11] */
	snd_emu10k1_write_op(emu, 0, 0x28, 0x40, 0x110, 0x100, &pc);
	snd_emu10k1_write_op(emu, 0, 0x29, 0x40, 0x111, 0x101, &pc);
	/* TOS out = GPR[0/1] */
	snd_emu10k1_write_op(emu, 6, 0x22, 0x40, 0x40, 0x100, &pc);
	snd_emu10k1_write_op(emu, 6, 0x23, 0x40, 0x40, 0x101, &pc);
	/* Mute Out2 */
	snd_emu10k1_write_op(emu, 6, 0x24, 0x40, 0x40, 0x40, &pc);
	snd_emu10k1_write_op(emu, 6, 0x25, 0x40, 0x40, 0x40, &pc);
	/* Enable LD2 Microphones */
	snd_emu10k1_write_op(emu, 6, 0x26, 0x40, 0x40, 0x100, &pc);
	snd_emu10k1_write_op(emu, 6, 0x27, 0x40, 0x40, 0x101, &pc);
	/* Input0 (AC97) -> Record */
	snd_emu10k1_write_op(emu, 6, 0x2a, 0x40, 0x40, 0x10, &pc);
	snd_emu10k1_write_op(emu, 6, 0x2b, 0x40, 0x40, 0x11, &pc);

	snd_emu10k1_ptr_write(emu, DBG, 0, 0);
}

/*************************************************************************
 * EMU10K1 init / done
 *************************************************************************/

static int snd_emu10k1_init(emu10k1_t * emu)
{
	int ch, idx;
	unsigned int silent_page;

	/* disable audio and lock cache */
	outl(HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE | HCFG_MUTEBUTTONENABLE, emu->port + HCFG);

	/* reset recording buffers */
	snd_emu10k1_ptr_write(emu, MICBS, 0, ADCBS_BUFSIZE_NONE);
	snd_emu10k1_ptr_write(emu, MICBA, 0, 0);
	snd_emu10k1_ptr_write(emu, FXBS, 0, ADCBS_BUFSIZE_NONE);
	snd_emu10k1_ptr_write(emu, FXBA, 0, 0);
	snd_emu10k1_ptr_write(emu, ADCBS, 0, ADCBS_BUFSIZE_NONE);
	snd_emu10k1_ptr_write(emu, ADCBA, 0, 0);

	/* disable channel interrupt */
	outl(DISABLE, emu->port + INTE);
	snd_emu10k1_ptr_write(emu, CLIEL, 0, 0);
	snd_emu10k1_ptr_write(emu, CLIEH, 0, 0);
	snd_emu10k1_ptr_write(emu, SOLEL, 0, 0);
	snd_emu10k1_ptr_write(emu, SOLEH, 0, 0);

	/* init envelope engine */
	for (ch = 0; ch < NUM_G; ch++) {
		emu->voices[ch].emu = emu;
		emu->voices[ch].number = ch;

		snd_emu10k1_ptr_write(emu, DCYSUSV, ch, ENV_OFF);
		snd_emu10k1_ptr_write(emu, IP, ch, 0);
		snd_emu10k1_ptr_write(emu, VTFT, ch, 0xffff);
		snd_emu10k1_ptr_write(emu, CVCF, ch, 0xffff);
		snd_emu10k1_ptr_write(emu, PTRX, ch, 0);
		snd_emu10k1_ptr_write(emu, CPF, ch, 0);
		snd_emu10k1_ptr_write(emu, CCR, ch, 0);

		snd_emu10k1_ptr_write(emu, PSST, ch, 0);
		snd_emu10k1_ptr_write(emu, DSL, ch, 0x10);
		snd_emu10k1_ptr_write(emu, CCCA, ch, 0);
		snd_emu10k1_ptr_write(emu, Z1, ch, 0);
		snd_emu10k1_ptr_write(emu, Z2, ch, 0);
		snd_emu10k1_ptr_write(emu, FXRT, ch, 0xd01c0000);

		snd_emu10k1_ptr_write(emu, ATKHLDM, ch, 0);
		snd_emu10k1_ptr_write(emu, DCYSUSM, ch, 0);
		snd_emu10k1_ptr_write(emu, IFATN, ch, 0xffff);
		snd_emu10k1_ptr_write(emu, PEFE, ch, 0);
		snd_emu10k1_ptr_write(emu, FMMOD, ch, 0);
		snd_emu10k1_ptr_write(emu, TREMFRQ, ch, 24);	/* 1 Hz */
		snd_emu10k1_ptr_write(emu, FM2FRQ2, ch, 24);	/* 1 Hz */
		snd_emu10k1_ptr_write(emu, TEMPENV, ch, 0);

		/*** these are last so OFF prevents writing ***/
		snd_emu10k1_ptr_write(emu, LFOVAL2, ch, 0);
		snd_emu10k1_ptr_write(emu, LFOVAL1, ch, 0);
		snd_emu10k1_ptr_write(emu, ATKHLDV, ch, 0);
		snd_emu10k1_ptr_write(emu, ENVVOL, ch, 0);
		snd_emu10k1_ptr_write(emu, ENVVAL, ch, 0);
	}


	/*
	 *  Init to 0x02109204 :
	 *  Clock accuracy    = 0     (1000ppm)
	 *  Sample Rate       = 2     (48kHz)
	 *  Audio Channel     = 1     (Left of 2)
	 *  Source Number     = 0     (Unspecified)
	 *  Generation Status = 1     (Original for Cat Code 12)
	 *  Cat Code          = 12    (Digital Signal Mixer)
	 *  Mode              = 0     (Mode 0)
	 *  Emphasis          = 0     (None)
	 *  CP                = 1     (Copyright unasserted)
	 *  AN                = 0     (Audio data)
	 *  P                 = 0     (Consumer)
	 */
	snd_emu10k1_ptr_write(emu, SPCS0, 0,
			SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 |
			SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC |
			SPCS_GENERATIONSTATUS | 0x00001200 |
			0x00000000 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT);
	snd_emu10k1_ptr_write(emu, SPCS1, 0,
			SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 |
			SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC |
			SPCS_GENERATIONSTATUS | 0x00001200 |
			0x00000000 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT);
	snd_emu10k1_ptr_write(emu, SPCS2, 0,
			SPCS_CLKACCY_1000PPM | SPCS_SAMPLERATE_48 |
			SPCS_CHANNELNUM_LEFT | SPCS_SOURCENUM_UNSPEC |
			SPCS_GENERATIONSTATUS | 0x00001200 |
			0x00000000 | SPCS_EMPHASIS_NONE | SPCS_COPYRIGHT);
	snd_emu10k1_init_efx(emu);

	/*
	 *  Clear page with silence & setup all pointers to this page
	 */
	memset(emu->silent_page, 0, PAGE_SIZE);
	silent_page = (unsigned int)virt_to_bus(emu->silent_page) << 1;
	for (idx = 0; idx < MAXPAGES; idx++)
		emu->ptb_pages[idx] = silent_page | idx; 
	snd_emu10k1_ptr_write(emu, PTB, 0, virt_to_bus((void *)emu->ptb_pages));
	snd_emu10k1_ptr_write(emu, TCB, 0, 0);	/* taken from original driver */
	snd_emu10k1_ptr_write(emu, TCBS, 0, 4);	/* taken from original driver */

	silent_page = (((unsigned int)virt_to_bus(emu->silent_page)) << 1) | MAP_PTI_MASK;
	for (ch = 0; ch < NUM_G; ch++) {
		snd_emu10k1_ptr_write(emu, MAPA, ch, silent_page);
		snd_emu10k1_ptr_write(emu, MAPB, ch, silent_page);
	}

	/*
	 *  Hokay, now enable the AUD bit
	 *   Enable Audio = 1
	 *   Mute Disable Audio = 0
	 *   Lock Tank Memory = 1
	 *   Lock Sound Memory = 0
	 *   Auto Mute = 1
	 */
	if (emu->model == 0x20 ||
	    emu->model == 0xc400 ||
	    (emu->model == 0x21 && emu->revision <= 3))
		outl(HCFG_AUDIOENABLE | HCFG_LOCKTANKCACHE | HCFG_AUTOMUTE, emu->port + HCFG);
	else
		// With on-chip joystick
		outl(HCFG_AUDIOENABLE | HCFG_LOCKTANKCACHE | HCFG_AUTOMUTE | HCFG_JOYENABLE, emu->port + HCFG);

#if 0
	{
	unsigned int tmp;
	/* FIXME: the following routine disables LiveDrive-II !! */
	// TOSLink detection
	emu->tos_link = 0;
	tmp = inl(emu->port + HCFG);
	if (tmp & (HCFG_GPINPUT0 | HCFG_GPINPUT1)) {
		outl(tmp|0x800, emu->port + HCFG);
		udelay(50);
		if (tmp != (inl(emu->port + HCFG) & ~0x800)) {
			emu->tos_link = 1;
			outl(tmp, emu->port + HCFG);
		}
	}
	}
#endif

	snd_emu10k1_intr_enable(emu, INTE_PCIERRORENABLE);

	emu->reserved_page = snd_emu10k1_synth_alloc(emu, 4096);
	
	return 0;
}

static int snd_emu10k1_done(emu10k1_t * emu)
{
	int ch;

	outl(0, emu->port + INTE);

	/*
	 *  Shutdown the chip
	 */
	for (ch = 0; ch < NUM_G; ch++)
		snd_emu10k1_ptr_write(emu, DCYSUSV, ch, ENV_OFF);
	for (ch = 0; ch < NUM_G; ch++) {
		snd_emu10k1_ptr_write(emu, VTFT, ch, 0);
		snd_emu10k1_ptr_write(emu, CVCF, ch, 0);
		snd_emu10k1_ptr_write(emu, PTRX, ch, 0);
		snd_emu10k1_ptr_write(emu, CPF, ch, 0);
	}

	/* reset recording buffers */
	snd_emu10k1_ptr_write(emu, MICBS, 0, 0);
	snd_emu10k1_ptr_write(emu, MICBA, 0, 0);
	snd_emu10k1_ptr_write(emu, FXBS, 0, 0);
	snd_emu10k1_ptr_write(emu, FXBA, 0, 0);
	snd_emu10k1_ptr_write(emu, FXWC, 0, 0);
	snd_emu10k1_ptr_write(emu, ADCBS, 0, ADCBS_BUFSIZE_NONE);
	snd_emu10k1_ptr_write(emu, ADCBA, 0, 0);
	snd_emu10k1_ptr_write(emu, TCBS, 0, TCBS_BUFFSIZE_16K);
	snd_emu10k1_ptr_write(emu, TCB, 0, 0);
	snd_emu10k1_ptr_write(emu, DBG, 0, 0x8000);

	/* disable channel interrupt */
	snd_emu10k1_ptr_write(emu, CLIEL, 0, 0);
	snd_emu10k1_ptr_write(emu, CLIEH, 0, 0);
	snd_emu10k1_ptr_write(emu, SOLEL, 0, 0);
	snd_emu10k1_ptr_write(emu, SOLEH, 0, 0);

	/* remove reserved page */
	snd_emu10k1_synth_free(emu, emu->reserved_page);

	/* disable audio and lock cache */
	outl(HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE | HCFG_MUTEBUTTONENABLE, emu->port + HCFG);
	snd_emu10k1_ptr_write(emu, PTB, 0, 0);

	return 0;
}

/*************************************************************************
 * ECARD functional implementation
 *************************************************************************/

/* In A1 Silicon, these bits are in the HC register */
#define HOOKN_BIT		(1L << 12)
#define HANDN_BIT		(1L << 11)
#define PULSEN_BIT		(1L << 10)

#define EC_GDI1			(1 << 13)
#define EC_GDI0			(1 << 14)

#define EC_NUM_CONTROL_BITS	20

#define EC_AC3_DATA_SELN	0x0001L
#define EC_EE_DATA_SEL		0x0002L
#define EC_EE_CNTRL_SELN	0x0004L
#define EC_EECLK		0x0008L
#define EC_EECS			0x0010L
#define EC_EESDO		0x0020L
#define EC_TRIM_CSN		0x0040L
#define EC_TRIM_SCLK		0x0080L
#define EC_TRIM_SDATA		0x0100L
#define EC_TRIM_MUTEN		0x0200L
#define EC_ADCCAL		0x0400L
#define EC_ADCRSTN		0x0800L
#define EC_DACCAL		0x1000L
#define EC_DACMUTEN		0x2000L
#define EC_LEDN			0x4000L

#define EC_SPDIF0_SEL_SHIFT	15
#define EC_SPDIF1_SEL_SHIFT	17
#define EC_SPDIF0_SEL_MASK	(0x3L << EC_SPDIF0_SEL_SHIFT)
#define EC_SPDIF1_SEL_MASK	(0x7L << EC_SPDIF1_SEL_SHIFT)
#define EC_SPDIF0_SELECT(_x)	(((_x) << EC_SPDIF0_SEL_SHIFT) & EC_SPDIF0_SEL_MASK)
#define EC_SPDIF1_SELECT(_x)	(((_x) << EC_SPDIF1_SEL_SHIFT) & EC_SPDIF1_SEL_MASK)
#define EC_CURRENT_PROM_VERSION 0x01	/* Self-explanatory.  This should
					 * be incremented any time the EEPROM's
					 * format is changed.  */

#define EC_EEPROM_SIZE		0x40	/* ECARD EEPROM has 64 16-bit words */

/* Addresses for special values stored in to EEPROM */
#define EC_PROM_VERSION_ADDR	0x20	/* Address of the current prom version */
#define EC_BOARDREV0_ADDR	0x21	/* LSW of board rev */
#define EC_BOARDREV1_ADDR	0x22	/* MSW of board rev */

#define EC_LAST_PROMFILE_ADDR	0x2f

#define EC_SERIALNUM_ADDR	0x30	/* First word of serial number.  The 
					 * can be up to 30 characters in length
					 * and is stored as a NULL-terminated
					 * ASCII string.  Any unused bytes must be
					 * filled with zeros */
#define EC_CHECKSUM_ADDR	0x3f	/* Location at which checksum is stored */


/* Most of this stuff is pretty self-evident.  According to the hardware 
 * dudes, we need to leave the ADCCAL bit low in order to avoid a DC 
 * offset problem.  Weird.
 */
#define EC_RAW_RUN_MODE		(EC_DACMUTEN | EC_ADCRSTN | EC_TRIM_MUTEN | \
				 EC_TRIM_CSN)


#define EC_DEFAULT_ADC_GAIN	0xC4C4
#define EC_DEFAULT_SPDIF0_SEL	0x0
#define EC_DEFAULT_SPDIF1_SEL	0x4

#define HC_EA			0x01L

/**************************************************************************
 * @func Clock bits into the Ecard's control latch.  The Ecard uses a
 *  control latch will is loaded bit-serially by toggling the Modem control
 *  lines from function 2 on the E8010.  This function hides these details
 *  and presents the illusion that we are actually writing to a distinct
 *  register.
 */

static void snd_emu10k1_ecard_write(emu10k1_t * emu, unsigned int value)
{
	unsigned short count;
	unsigned int data;
	unsigned long hc_port;
	unsigned int hc_value;

	hc_port = emu->port + HCFG;
	hc_value = inl(hc_port) & ~(HOOKN_BIT | HANDN_BIT | PULSEN_BIT);
	outl(hc_value, hc_port);

	for (count = 0; count < EC_NUM_CONTROL_BITS; count++) {

		/* Set up the value */
		data = ((value & 0x1) ? PULSEN_BIT : 0);
		value >>= 1;

		outl(hc_value | data, hc_port);

		/* Clock the shift register */
		outl(hc_value | data | HANDN_BIT, hc_port);
		outl(hc_value | data, hc_port);
	}

	/* Latch the bits */
	outl(hc_value | HOOKN_BIT, hc_port);
	outl(hc_value, hc_port);
}

/**************************************************************************
 * @func Set the gain of the ECARD's CS3310 Trim/gain controller.  The
 * trim value consists of a 16bit value which is composed of two
 * 8 bit gain/trim values, one for the left channel and one for the
 * right channel.  The following table maps from the Gain/Attenuation
 * value in decibels into the corresponding bit pattern for a single
 * channel.
 */

static void snd_emu10k1_ecard_setadcgain(emu10k1_t * emu,
					 unsigned short gain)
{
	unsigned int bit;

	/* Enable writing to the TRIM registers */
	snd_emu10k1_ecard_write(emu, emu->ecard_ctrl & ~EC_TRIM_CSN);

	/* Do it again to insure that we meet hold time requirements */
	snd_emu10k1_ecard_write(emu, emu->ecard_ctrl & ~EC_TRIM_CSN);

	for (bit = (1 << 15); bit; bit >>= 1) {
		unsigned int value;
		
		value = emu->ecard_ctrl & ~(EC_TRIM_CSN | EC_TRIM_SDATA);

		if (gain & bit)
			value |= EC_TRIM_SDATA;

		/* Clock the bit */
		snd_emu10k1_ecard_write(emu, value);
		snd_emu10k1_ecard_write(emu, value | EC_TRIM_SCLK);
		snd_emu10k1_ecard_write(emu, value);
	}

	snd_emu10k1_ecard_write(emu, emu->ecard_ctrl);
}

static int snd_emu10k1_ecard_init(emu10k1_t * emu)
{
	unsigned int hc_value;

	/* Set up the initial settings */
	emu->ecard_ctrl = EC_RAW_RUN_MODE |
			  EC_SPDIF0_SELECT(EC_DEFAULT_SPDIF0_SEL) |
			  EC_SPDIF1_SELECT(EC_DEFAULT_SPDIF1_SEL);

	/* Step 0: Set the codec type in the hardware control register 
	 * and enable audio output */
	hc_value = inl(emu->port + HCFG);
	outl(hc_value | HC_EA | 0x10000, emu->port + HCFG);
	inl(emu->port + HCFG);

	/* Step 1: Turn off the led and deassert TRIM_CS */
	snd_emu10k1_ecard_write(emu, EC_ADCCAL | EC_LEDN | EC_TRIM_CSN);

	/* Step 2: Calibrate the ADC and DAC */
	snd_emu10k1_ecard_write(emu, EC_DACCAL | EC_LEDN | EC_TRIM_CSN);

	/* Step 3: Wait for awhile;   XXX We can't get away with this
	 * under a real operating system; we'll need to block and wait that
	 * way. */
	snd_emu10k1_wait(emu, 48000);

	/* Step 4: Switch off the DAC and ADC calibration.  Note
	 * That ADC_CAL is actually an inverted signal, so we assert
	 * it here to stop calibration.  */
	snd_emu10k1_ecard_write(emu, EC_ADCCAL | EC_LEDN | EC_TRIM_CSN);

	/* Step 4: Switch into run mode */
	snd_emu10k1_ecard_write(emu, emu->ecard_ctrl);

	/* Step 5: Set the analog input gain */
	snd_emu10k1_ecard_setadcgain(emu, EC_DEFAULT_ADC_GAIN);

	return 0;
}

/*
 *  Create the EMU10K1 instance
 */

static int snd_emu10k1_free(emu10k1_t * emu)
{
	snd_emu10k1_proc_done(emu);
	snd_emu10k1_done(emu);
	if (emu->memhdr)
		snd_emux_memhdr_free(emu->memhdr);
	if (emu->silent_page)
		snd_free_pages(emu->silent_page, EMUPAGESIZE);
	if (emu->ptb_pages)
		snd_free_pages((void *)emu->ptb_pages, 32 * 1024);
	snd_magic_kfree(emu);
	return 0;
}

int snd_emu10k1_create(snd_card_t * card,
		       struct pci_dev * pci,
		       snd_dma_t * dma1ptr,
		       snd_dma_t * dma2ptr,
		       snd_irq_t * irqptr,
		       emu10k1_t ** remu)
{
	emu10k1_t *emu;
	unsigned short cmdw;
	unsigned char cmdb;
	int err;
	static snd_device_ops_t ops = {
		(snd_dev_free_t *)snd_emu10k1_free,
		NULL,
		NULL
	};
	
	*remu = NULL;
	emu = snd_magic_kcalloc(emu10k1_t, 0, GFP_KERNEL);
	if (emu == NULL)
		return -ENOMEM;
	emu->card = card;
	spin_lock_init(&emu->reg_lock);
	spin_lock_init(&emu->emu_lock);
	spin_lock_init(&emu->voice_lock);
	spin_lock_init(&emu->synth_lock);
	init_MUTEX(&emu->ptb_lock);
	emu->pci = pci;
	emu->dma1ptr = dma1ptr;
	emu->dma2ptr = dma2ptr;
	emu->irqptr = irqptr;
	emu->synth = NULL;
	emu->get_synth_voice = NULL;
	emu->port = pci_resource_start(pci, 0);

	emu->ptb_pages = snd_malloc_pages(32 * 1024, NULL, 0);
	if (emu->ptb_pages == NULL || ((long)virt_to_bus((void *)emu->ptb_pages) & ~0x7fffffffUL)) {
		snd_printk(__PRETTY_FUNCTION__ ": PTB pages ptr > 2GB limit, giving up\n");
		snd_emu10k1_free(emu);
		return -ENOMEM;
	}
	emu->silent_page = snd_malloc_pages(EMUPAGESIZE, NULL, 0);
	if (emu->silent_page == NULL || ((long)virt_to_bus(emu->silent_page) & ~0x7fffffffUL)) {
		snd_printk(__PRETTY_FUNCTION__ ": Silent page ptr > 2GB limit, giving up\n");
		snd_emu10k1_free(emu);
		return -ENOMEM;
	}
	emu->memhdr = snd_emux_memhdr_new(EMUPAGESIZE * MAXPAGES);
	if (emu->memhdr == NULL) {
		snd_emu10k1_free(emu);
		return -ENOMEM;
	}
	emu->memhdr->block_extra_size = sizeof(snd_emu10k1_memblk_arg_t);

	pci_set_master(pci);
	pci_read_config_word(pci, PCI_COMMAND, &cmdw);
	if ((cmdw & PCI_COMMAND_IO) != PCI_COMMAND_IO) {
		cmdw |= PCI_COMMAND_IO;
		pci_write_config_word(pci, PCI_COMMAND, cmdw);
	}
	pci_set_master(pci);
	pci_read_config_byte(pci, PCI_LATENCY_TIMER, &cmdb);
	if (cmdb < 32)
		cmdb = 32;
	pci_write_config_byte(pci, PCI_LATENCY_TIMER, cmdb);
	/* read revision & serial */
	pci_read_config_byte(pci, PCI_REVISION_ID, (char *)&emu->revision);
	pci_read_config_dword(pci, PCI_SUBSYSTEM_VENDOR_ID, &emu->serial);
	pci_read_config_word(pci, PCI_SUBSYSTEM_ID, &emu->model);
	emu->APS = emu->serial == 0x40011102;

	if (emu->APS) {
		if ((err = snd_emu10k1_ecard_init(emu)) < 0) {
			snd_emu10k1_free(emu);
			return err;
		}
	}

	if ((err = snd_emu10k1_init(emu)) < 0) {
		snd_emu10k1_free(emu);
		return err;
	}

	if ((err = snd_device_new(card, SND_DEV_LOWLEVEL, emu, 0, &ops, NULL)) < 0) {
		snd_emu10k1_free(emu);
		return err;
	}

	snd_emu10k1_proc_init(emu);

	*remu = emu;
	return 0;
}


/*
 *  INIT part
 */

static int __init alsa_emu10k1_init(void)
{
	return 0;
}

static void __exit alsa_emu10k1_exit(void)
{
}

module_init(alsa_emu10k1_init)
module_exit(alsa_emu10k1_exit)

EXPORT_SYMBOL(snd_emu10k1_create);
EXPORT_SYMBOL(snd_emu10k1_mixer);
/* irq.c */
EXPORT_SYMBOL(snd_emu10k1_interrupt);
/* memory.c */
EXPORT_SYMBOL(snd_emu10k1_synth_alloc);
EXPORT_SYMBOL(snd_emu10k1_synth_free);
EXPORT_SYMBOL(snd_emu10k1_synth_bzero);
EXPORT_SYMBOL(snd_emu10k1_synth_copy_from_user);
/* voice.c */
EXPORT_SYMBOL(snd_emu10k1_voice_alloc);
EXPORT_SYMBOL(snd_emu10k1_voice_free);
/* emumpu401.c */
EXPORT_SYMBOL(snd_emu10k1_midi);
/* emupcm.c */
EXPORT_SYMBOL(snd_emu10k1_pcm);
EXPORT_SYMBOL(snd_emu10k1_pcm_mic);
EXPORT_SYMBOL(snd_emu10k1_pcm_efx);
/* io.c */
EXPORT_SYMBOL(snd_emu10k1_ptr_read);
EXPORT_SYMBOL(snd_emu10k1_ptr_write);
