/*
 *  Init routines for SB-LIVE! EMU10k1 chips
 *
 */
#include "driver.h"
#include "info.h"
#include "sndpci.h"
#include "control.h"
#include "emu10k1.h"
#include "dma.h"


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

static void snd_emu10k1_init_efx(emu10k1_t *emu)
{
        int i;
        unsigned long z, w, x, y, pc = 16;

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

        for (i = 0; i < 256; i++)
                snd_emu10k1_efx_write(emu, 0x00000100 + i, 0);

        w = 1;
        x = 1;
        y = 0x40;
        z = 0x100;
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2), (x << 10) | y);
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2 + 1), (6 << 20) | (z << 10) | w);
        ++pc;

        w = 0x100;
        x = 0x100;
        y = emu->APS ? 0x17 : 0x40;
        z = 0x101;
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2), (x << 10) | y);
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2 + 1), (6 << 20) | (z << 10) | w);
        ++pc;

        w = 0x101;
        x = 0x40;
        y = 0x13;
        z = 0x21;
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2), (x << 10) | y);
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2 + 1), (6 << 20) | (z << 10) | w);
        ++pc;

        w = 0x101;
        x = 0x40;
        y = 0x13;
        z = 0x23;
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2), (x << 10) | y);
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2 + 1), (6 << 20) | (z << 10) | w);
        ++pc;

        w = 0x40;
        x = 0x40;
        y = 0x40;
        z = 0x25;
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2), (x << 10) | y);
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2 + 1), (6 << 20) | (z << 10) | w);
        ++pc;

        z = 0x27;
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2), (x << 10) | y);
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2 + 1), (6 << 20) | (z << 10) | w);
        ++pc;

        w = 0x101;
        x = 0x40;
        y = 0x13;
        z = 0x29;
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2), (x << 10) | y);
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2 + 1), (6 << 20) | (z << 10) | w);
        ++pc;

        w = 0x40;
        x = 0x11;
        y = 0x40;
        z = 0x2B;
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2), (x << 10) | y);
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2 + 1), (6 << 20) | (z << 10) | w);
        ++pc;

        w = 0;
        x = 0;
        y = 0x40;
        z = 0x102;
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2), (x << 10) | y);
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2 + 1), (6 << 20) | (z << 10) | w);
        ++pc;

        w = 0x102;
        x = 0x102;
        y = emu->APS ? 0x16 : 0x40;
        z = 0x103;
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2), (x << 10) | y);
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2 + 1), (6 << 20) | (z << 10) | w);
        ++pc;

        w = 0x103;
        x = 0x40;
        y = 0x12;
        z = 0x20;
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2), (x << 10) | y);
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2 + 1), (6 << 20) | (z << 10) | w);
        ++pc;


        w = 0x103;
        x = 0x40;
        y = 0x12;
        z = 0x22;
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2), (x << 10) | y);
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2 + 1), (6 << 20) | (z << 10) | w);
        ++pc;

        w = 0x40;
        x = 0x40;
        y = 0x40;
        z = 0x24;
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2), (x << 10) | y);
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2 + 1), (6 << 20) | (z << 10) | w);
        ++pc;

        z = 0x26;
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2), (x << 10) | y);
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2 + 1), (6 << 20) | (z << 10) | w);
        ++pc;

        w = 0x103;
        x = 0x40;
        y = 0x12;
        z = 0x28;
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2), (x << 10) | y);
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2 + 1), (6 << 20) | (z << 10) | w);
        ++pc;

        w = 0x40;
        x = 0x10;
        y = 0x40;
        z = 0x2A;
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2), (x << 10) | y);
        snd_emu10k1_efx_write(emu, 0x00000400 + (pc * 2 + 1), (6 << 20) | (z << 10) | w);
        ++pc;

        snd_emu10k1_synth_write(emu, 0x00520000, 0);
}

/*************************************************************************
 * EMU10k1 AC'97 init
 *************************************************************************/
static void snd_emu10k1_ac97_init(emu10k1_t *emu)
{
	unsigned char ready = 0;
	unsigned short data = 0;

	snd_mutex_lock(emu, emu);
	outb(AC97_POWERDOWN, emu->port + AC97A);
	outw(0, emu->port + AC97D);

	ready = inb(emu->port + AC97A);
	data = inw(emu->port + AC97D);
	snd_mutex_unlock(emu, emu);

	snd_printd("ac97_read: ready(x) 0x%.2X  0x%.4X\n", ready, data);

#if 0
	/* AC'97 start cold reset (just to be safe) */
	snd_emu10k1_ac97_write(emu, AC97_POWERDOWN, AC97_PWD_PR5);

	/* AC'97 end cold reset and start warm reset to start the bitclk */
	snd_emu10k1_ac97_write(emu, AC97_POWERDOWN, AC97_PWD_PR4);

	/* AC'97 end warm reset */
	snd_emu10k1_ac97_write(emu, AC97_POWERDOWN, 0);

	/* AC'97 register reset  */
	snd_emu10k1_ac97_write(emu, AC97_RESET, 0);
#endif
}

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

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

        /* disable audio and lock cache */
        outl(0x0e, emu->port + HCFG);

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

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

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

                snd_emu10k1_synth_write(emu, ch | DCYSUSV, ENV_OFF);
                snd_emu10k1_synth_write(emu, ch | IP, 0);
                snd_emu10k1_synth_write(emu, ch | VTFT, 0xFFFFL);
                snd_emu10k1_synth_write(emu, ch | CVCF, 0xFFFFL);
                snd_emu10k1_synth_write(emu, ch | PTRX, 0L);
                snd_emu10k1_synth_write(emu, ch | CPF, 0L);
                snd_emu10k1_synth_write(emu, ch | CCTRL, 0L);

                snd_emu10k1_synth_write(emu, ch | PSST, 0L);
                snd_emu10k1_synth_write(emu, ch | CSL, 0x10L);
                snd_emu10k1_synth_write(emu, ch | CCCA, 0L);
                snd_emu10k1_synth_write(emu, ch | Z1, 0L);
                snd_emu10k1_synth_write(emu, ch | Z2, 0L);
                snd_emu10k1_synth_write(emu, ch | 0x000B0000, 0xD01C0000);

                snd_emu10k1_synth_write(emu, ch | ATKHLD, 0);
                snd_emu10k1_synth_write(emu, ch | DCYSUS, 0);
                snd_emu10k1_synth_write(emu, ch | IFATN, 0xffff);
                snd_emu10k1_synth_write(emu, ch | PEFE, 0);
                snd_emu10k1_synth_write(emu, ch | FMMOD, 0);
                snd_emu10k1_synth_write(emu, ch | TREMFRQ, 24); /* 1 Hz */
                snd_emu10k1_synth_write(emu, ch | FM2FRQ2, 24); /* 1 Hz */
                snd_emu10k1_synth_write(emu, ch | TEMPENV, 0);

                /*** these are last so OFF prevents writing ***/
                snd_emu10k1_synth_write(emu, ch | LFOVAL2, 0);
                snd_emu10k1_synth_write(emu, ch | LFOVAL1, 0);
                snd_emu10k1_synth_write(emu, ch | ATKHLDV, 0);
                snd_emu10k1_synth_write(emu, ch | ENVVOL, 0);
                snd_emu10k1_synth_write(emu, ch | ENVVAL, 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_synth_write(emu, SPCS0, 0x02109204);        // SPDIF0
        snd_emu10k1_synth_write(emu, SPCS1, 0x02109204);        // SPDIF1
        snd_emu10k1_synth_write(emu, SPCS2, 0x02109204);        // SPDIF2 & SPDIF3
        snd_emu10k1_init_efx(emu);

        /*
         *  Clear page with silence & setup all pointers to this page
         */
//	memset(emu->silent_page, 0, PAGE_SIZE);
	memset(emu->silent_page, 0, EMUPAGESIZE);
        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_synth_write(emu, PTB, virt_to_bus((void*)emu->ptb_pages));
        snd_emu10k1_synth_write(emu, TMBA, 0);  /* taken from original driver */
        snd_emu10k1_synth_write(emu, TMBS, 4);  /* taken from original driver */

        silent_page = (((unsigned int)virt_to_bus(emu->silent_page)) << 1) | 0x1fff;
        for (ch = 0; ch < NUM_G; ch++) {
                snd_emu10k1_synth_write(emu, ch | MAPA, silent_page);
                snd_emu10k1_synth_write(emu, ch | MAPB, 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->revision < 6)
                outl(1+4+16, emu->port + HCFG);
        else
		// With on-chip joystick
		outl(1+4+16+512, emu->port + HCFG);
        // TOSLink detection
        emu->tos_link = 0;
        tmp = inl(emu->port + HCFG);
	snd_printd("emu10k1_init: HW Config = 0x%.8X\n", tmp);
        if (tmp & 0x6000) {
                volatile unsigned delay;

                outl(tmp|0x800, emu->port + HCFG);
                for (delay = 0; delay < 512; delay++);  // small delay

                if (tmp != (inl(emu->port + HCFG) & ~0x800)) {
                        emu->tos_link = 1;
                        outl(tmp, emu->port + HCFG);
                }
        }

        snd_emu10k1_intr_enable(emu, ENB_PCI);

        emu->reserved_page = snd_emu10k1_synth_malloc(emu, 4096, &emu->reserved_page_addr);

	snd_emu10k1_ac97_init(emu);

        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_synth_write(emu, DCYSUSV | ch, ENV_OFF);
        for (ch = 0; ch < NUM_G; ch++) {
                snd_emu10k1_synth_write(emu, VTFT | ch, 0);
                snd_emu10k1_synth_write(emu, CVCF | ch, 0);
                snd_emu10k1_synth_write(emu, PTRX | ch, 0);
                snd_emu10k1_synth_write(emu, CPF | ch, 0);
        }

        /* reset recording buffers */
        snd_emu10k1_synth_write(emu, MICBS, 0);
        snd_emu10k1_synth_write(emu, MICBA, 0);
        snd_emu10k1_synth_write(emu, FXBS, 0);
        snd_emu10k1_synth_write(emu, FXBA, 0);
        snd_emu10k1_synth_write(emu, 0x00430000, 0);
        snd_emu10k1_synth_write(emu, ADCBS, 0);
        snd_emu10k1_synth_write(emu, ADCBA, 0);
        snd_emu10k1_synth_write(emu, TMBS, 0);
        snd_emu10k1_synth_write(emu, TMBA, 0);
        snd_emu10k1_synth_write(emu, 0x00520000, 0x8000);

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

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

        /* disable audio and lock cache */
        outl(0x0e, emu->port + HCFG);
        snd_emu10k1_synth_write(emu, PTB, 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 int 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;
}

static void
snd_emu10k1_proc_read (snd_info_buffer_t * buffer,
        void *private_data)
{
        emu10k1_t *emu= (emu10k1_t *) private_data;

        emu= emu;
        snd_iprintf (buffer, "SB-LIVE! EMU10k1\n\n");
}

static void
snd_emu10k1_proc_init (emu10k1_t * emu)
{
        snd_info_entry_t *entry;

        emu= emu;
        if ((entry= snd_info_create_entry (emu->card, "sblive")) != NULL)
        {
                entry->private_data= emu;
                entry->mode= S_IFREG | S_IRUGO | S_IWUSR;
                entry->t.text.read_size= 256;
                entry->t.text.read= snd_emu10k1_proc_read;
                if (snd_info_register (entry) < 0)
                {
                        snd_info_free_entry (entry);
                        entry= NULL;
                }
        }
        emu->proc_entry= entry;
}

static void
snd_emu10k1_proc_done (emu10k1_t * emu)
{
        if (emu->proc_entry)
        {
                snd_info_unregister (emu->proc_entry);
                emu->proc_entry= NULL;
        }
}

emu10k1_t *
snd_emu10k1_create (snd_card_t * card,
        struct snd_pci_dev *pci,
        snd_dma_t * dma1ptr,
        snd_dma_t * dma2ptr,
        snd_dma_t * dma3ptr,
        snd_irq_t * irqptr)
{
        emu10k1_t *emu;
        unsigned short cmdw;
        unsigned char cmdb;
        int err;

        emu= (emu10k1_t *) snd_calloc (sizeof (emu10k1_t));
        if (!emu)
                return NULL;
        snd_mutex_prepare (emu, reg);
        snd_mutex_prepare (emu, emu);
        snd_mutex_prepare (emu, voice);
        snd_mutex_prepare (emu, ptb);
        emu->card= card;
        emu->pci= pci;
        emu->dma1ptr= dma1ptr;
        emu->dma2ptr= dma2ptr;
        emu->dma3ptr= dma3ptr;
        emu->irqptr= irqptr;
        emu->port= PCI_IO_ADDR(pci->base_address[0]);
        emu->ptb_pages = (volatile unsigned int*) snd_malloc_pages(32 * 1024, NULL, 1);
        if ((emu->ptb_pages == NULL) ||
           ((long)virt_to_bus((void*)emu->ptb_pages) & ~0x7fffffffUL)) {
                snd_emu10k1_free(emu);
//              return -ENOMEM;
                return NULL;
        }
        emu->silent_page = snd_malloc_pages(EMUPAGESIZE, NULL, 1);
        if ((emu->silent_page == NULL) ||
           ((long)virt_to_bus(emu->silent_page) & ~0x7fffffffUL)) {
                snd_emu10k1_free(emu);
//              return -ENOMEM;
                return NULL;
        }

//      snd_pci_set_master(pci);  // doesn't exist in QNXNTO
        snd_pci_read_config_word(pci, PCI_COMMAND, &cmdw);
        if ((cmdw & PCI_COMMAND_IO) != PCI_COMMAND_IO) {
                cmdw |= PCI_COMMAND_IO;
                snd_pci_write_config_word(pci, PCI_COMMAND, cmdw);
        }
//      snd_pci_set_master(pci);  // doesn't exist in QNXNTO
        snd_pci_read_config_byte(pci, PCI_LATENCY_TIMER, &cmdb);
        if (cmdb < 32)
                cmdb = 32;
        snd_pci_write_config_byte(pci, PCI_LATENCY_TIMER, cmdb);
        /* read revision & serial */
        snd_pci_read_config_byte(pci, PCI_REVISION_ID, (char *)&emu->revision);
        snd_pci_read_config_dword(pci, PCI_SUBSYSTEM_VENDOR_ID, &emu->serial);
        emu->APS = emu->serial == PCI_SUBSYSTEM_VENDOR_ID_EMU_APS;

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

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

        snd_emu10k1_proc_init (emu);
        return (emu);
}

void
snd_emu10k1_free (emu10k1_t *emu)
{
        snd_emu10k1_proc_done (emu);
        snd_emu10k1_done(emu);

        if (emu->ptb_pages)
                snd_free ((void*) emu->ptb_pages, 32 * 1024);
        if (emu->silent_page)
                snd_free (emu->silent_page, EMUPAGESIZE);

        snd_free (emu, sizeof (emu10k1_t));
}

