/*
 * sound/ad1848.c
 * 
 * Driver for Microsoft Sound System/Windows Sound System (mss)
 * -compatible boards. This includes:
 * 
 * AD1848, CS4248, CS423x, OPTi931, Yamaha OPL/SAx and many others.
 *
 * Copyright Luigi Rizzo, 1997,1998
 * Copyright by Hannu Savolainen 1994, 1995
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer
 *    in the documentation and/or other materials provided with the
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * Full data sheets in PDF format for the MSS-compatible chips
 * are available at
 *
 *	http://www.crystal.com/ for the CS42XX series, or
 *	http://www.opti.com/	for the OPTi931
 */

#include <i386/isa/snd/sound.h>
#if NPCM > 0

/*
 * board-specific include files
 */

#include <i386/isa/snd/mss.h>

/*
 * prototypes for procedures exported in the device descriptor
 */

static int mss_probe(struct isa_device *dev);
static int mss_attach(struct isa_device *dev);

static d_open_t mss_open;
static d_close_t mss_close;
static d_ioctl_t mss_ioctl;
static irq_proc_t mss_intr;
static irq_proc_t opti931_intr;
static  snd_callback_t mss_callback;
   
/*
 * prototypes for local functions
 */

static void mss_reinit(snddev_info *d);
static int AD_WAIT_INIT(snddev_info *d, int x);
static int mss_mixer_set(snddev_info *d, int dev, int value);
static int mss_set_recsrc(snddev_info *d, int mask);
static void ad1848_mixer_reset(snddev_info *d);

static void opti_write(int io_base, u_char reg, u_char data);
static u_char opti_read(int io_base, u_char reg);
static void ad_write(snddev_info *d, int reg, u_char data);
static void ad_write_cnt(snddev_info *d, int reg, u_short data);
static int ad_read(snddev_info *d, int reg);

/*
 * device descriptors for the boards supported by this module.
 */
snddev_info mss_op_desc = {
    "mss",

    SNDCARD_MSS,
    mss_probe,
    mss_attach,

    mss_open,
    mss_close,
    NULL /* mss_read */,
    NULL /* mss_write */,
    mss_ioctl,
    sndselect /* mss_select */,

    mss_intr,
    mss_callback ,

    DSP_BUFFSIZE,	/* bufsize */

    AFMT_STEREO |
    AFMT_U8 | AFMT_S16_LE | AFMT_MU_LAW | AFMT_A_LAW,	/* audio formats */
    /*
     * the enhanced boards also have AFMT_IMA_ADPCM | AFMT_S16_BE
     * but we do not use these modes.
     */
} ;

/*
 * mss_probe() is the probe routine. Note, it is not necessary to
 * go through this for PnP devices, since they are already
 * indentified precisely using their PnP id.
 *
 * The base address supplied in the device refers to the old MSS
 * specs where the four 4 registers in io space contain configuration
 * information. Some boards (as an example, early MSS boards)
 * has such a block of registers, whereas others (generally CS42xx)
 * do not.  In order to distinguish between the two and do not have
 * to supply two separate probe routines, the flags entry in isa_device
 * has a bit to mark this.
 *
 */

static int
mss_probe(struct isa_device *dev)
{
    u_char   tmp;
    int irq = ffs(dev->id_irq) - 1;
 
    bzero(&pcm_info[dev->id_unit], sizeof(pcm_info[dev->id_unit]) );
    if (dev->id_iobase == -1) {
        dev->id_iobase = 0x530;
        BVDDB(printf("mss_probe: no address supplied, try default 0x%x\n",
	    dev->id_iobase));
    }
    if (snd_conflict(dev->id_iobase))
	return 0 ;

    if ( !(dev->id_flags & DV_F_TRUE_MSS) ) /* Has no IRQ/DMA registers */
	goto mss_probe_end;

    /*
     * Check if the IO port returns valid signature. The original MS
     * Sound system returns 0x04 while some cards
     * (AudioTriX Pro for example) return 0x00 or 0x0f.
     */

    tmp = inb(dev->id_iobase + 3);
    if (tmp == 0xff) {	/* Bus float */
	BVDDB(printf("I/O address inactive (%x), try pseudo_mss\n", tmp));
	dev->id_flags &= ~DV_F_TRUE_MSS ;
	goto mss_probe_end;
    }
    tmp &= 0x3f ;
    if (tmp != 0x04 && tmp != 0x0f && tmp != 0x00) {
	BVDDB(printf("No MSS signature detected on port 0x%x (0x%x)\n",
		   dev->id_iobase, inb(dev->id_iobase + 3)));
	return 0;
    }
    if (irq > 11) {
	printf("MSS: Bad IRQ %d\n", irq);
	return 0;
    }
    if (dev->id_drq != 0 && dev->id_drq != 1 && dev->id_drq != 3) {
	printf("MSS: Bad DMA %d\n", dev->id_drq);
	return 0;
    }
    if (inb(dev->id_iobase + 3) & 0x80) {
	/* 8-bit board: only drq1/3 and irq7/9 */
	if (dev->id_drq == 0) {
	    printf("MSS: Can't use DMA0 with a 8 bit card/slot\n");
	    return 0;
	}
	if (irq != 7 && irq != 9) {
	    printf("MSS: Can't use IRQ%d with a 8 bit card/slot\n", irq);
	    return 0;
	}
    }
mss_probe_end:
    return mss_detect(dev) ? 8 : 0 ; /* mss uses 8 regs */
}

/*
 * the address passed as io_base for mss_attach is also the old
 * MSS base address (e.g. 0x530). The codec is four locations ahead.
 * Note that the attach routine for PnP devices might support
 * device-specific initializations.
 */

static int
mss_attach(struct isa_device *dev)
{
    snddev_info *d = &(pcm_info[dev->id_unit]);

    printf("mss_attach <%s>%d at 0x%x irq %d dma %d:%d flags 0x%x\n",
	d->name, dev->id_unit,
	d->io_base, d->irq, d->dbuf_out.chan, d->dbuf_in.chan, dev->id_flags);

    dev->id_alive = 8 ; /* number of io ports */
    /* should be already set but just in case... */

    if ( dev->id_flags & DV_F_TRUE_MSS ) {
	/* has IRQ/DMA registers, set IRQ and DMA addr */
	static char     interrupt_bits[12] = {
	    -1, -1, -1, -1, -1, 0x28, -1, 0x08, -1, 0x10, 0x18, 0x20
	};
	static char     dma_bits[4] = { 1, 2, 0, 3 };
	char	bits ;

	if (d->irq == -1 || (bits = interrupt_bits[d->irq]) == -1) {
	    dev->id_irq = 0 ; /* makk invalid irq */
	    return 0 ;
	}

	outb(dev->id_iobase, bits | 0x40);	/* config port */
	if ((inb(dev->id_iobase + 3) & 0x40) == 0) /* version port */
	    printf("[IRQ Conflict?]");

	/* Write IRQ+DMA setup */
	if ( ! FULL_DUPLEX(d) ) /* single chan dma */
	    outb(dev->id_iobase, bits | dma_bits[d->dbuf_out.chan]);
	else {
	    if (d->dbuf_out.chan == 0 && d->dbuf_in.chan == 1)
		bits |= 5 ;
	    else if (d->dbuf_out.chan == 1 && d->dbuf_in.chan == 0)
		bits |= 6 ;
	    else if (d->dbuf_out.chan == 3 && d->dbuf_in.chan == 0)
		bits |= 7 ;
	    else {
		printf("invalid dual dma config %d:%d\n",
			d->dbuf_out.chan, d->dbuf_in.chan);
		dev->id_irq = 0 ;
		dev->id_alive = 0 ; /* this makes attach fail. */
		return 0 ;
	    }
	    outb(dev->id_iobase, bits );
	}
    }
    if (1) { /* machine-specific code for the Toshiba Libretto */
        u_char r6, r9;
        outb( 0x370, 6 /* dma config */ );
        outb( 0x371, 0xa9 /* config: DMA-B for rec, DMA-A for play */);
        r6 = inb( 0x371 /* read */ ); 
        outb( 0x370, 0xa /* version */ );
        r9 = inb( 0x371 /* read */ );
        DEB(printf("Yamaha: ver 0x%x DMA config 0x%x\n", r6, r9);)
	/*
	 * yamaha - set volume to max
	 */
	outb( 0x370, 7 /* volume left */ );
	outb( 0x371, 0 /* max level */ );
	outb( 0x370, 8 /* volume right */ );
	outb( 0x371, 0 /* max level */ );
    }
    if ( FULL_DUPLEX(d) )
	d->audio_fmt |= AFMT_FULLDUPLEX ;
    if (d->bd_id == MD_YM0020) {
	DDB(printf("setting up yamaha registers\n"));
	outb(0x370, 6 /* dma config */ ) ;
	if (FULL_DUPLEX(d))
	    outb(0x371, 0xa9 ); /* use both dma chans */
	else
	    outb(0x371, 0x8b ); /* use low dma chan */
    }
    mss_reinit(d);
    ad1848_mixer_reset(d);
    return 0;
}

static int
mss_open(dev_t dev, int flags, int mode, struct proc * p)
{
    int unit;
    snddev_info *d;
    u_long s;
    
    dev = minor(dev);
    unit = dev >> 4 ;
    dev &= 0xf ;
    d = &pcm_info[unit] ;       
    
    s = spltty();
    /*
     * This was meant to support up to 2 open descriptors for the
     * some device, and check proper device usage on open.
     * Unfortunately, the kernel will trap all close() calls but
     * the last one, with the consequence that we cannot really
     * keep track of which channels are busy.
     * So, the correct tests cannot be done :( and we must rely
     * on the locks on concurrent operations of the same type and
     * on some approximate tests...
     */
    
    if (dev == SND_DEV_AUDIO)
	d->flags |= SND_F_BUSY_AUDIO ;
    else if (dev == SND_DEV_DSP)
	d->flags |= SND_F_BUSY_DSP ;
    else if (dev == SND_DEV_DSP16)
	d->flags |= SND_F_BUSY_DSP16 ;
    if ( d->flags & SND_F_BUSY )
	splx(s); /* device was already set, no need to reinit */
    else {
	/*
	 * device was idle. Do the necessary initialization,
	 * but no need keep interrupts blocked.
	 * will not get them
	 */

	splx(s);
	d->play_speed = d->rec_speed = DSP_DEFAULT_SPEED ;
	d->flags |= SND_F_BUSY ;

	d->wsel.si_pid = 0;
	d->wsel.si_flags = 0;

	d->rsel.si_pid = 0;
	d->rsel.si_flags = 0;

	d->dbuf_out.total = d->dbuf_out.prev_total = 0 ;
	d->dbuf_in.total = d->dbuf_in.prev_total = 0 ;

	if (flags & O_NONBLOCK)
	    d->flags |= SND_F_NBIO ;

	switch (dev) {
	default :
	case SND_DEV_AUDIO :
	    d->play_fmt = d->rec_fmt = AFMT_MU_LAW ;
	    break ;
	case SND_DEV_DSP :
	    d->play_fmt = d->rec_fmt = AFMT_U8 ;
	    break ;
	case SND_DEV_DSP16 :
	    d->play_fmt = d->rec_fmt = AFMT_S16_LE ;
	    break;
	}
	ask_init(d); /* and reset buffers... */
    }
    return 0 ;
}

static int
mss_close(dev_t dev, int flags, int mode, struct proc * p)
{
    int unit;
    snddev_info *d;
    u_long s;

    dev = minor(dev);
    unit = dev >> 4 ;
    dev &= 0xf;
    d = &pcm_info[unit] ;

    /*
     * We will only get a single close call when the last reference
     * to the device is gone. But we must handle ourselves references
     * through different devices.
     */

    s = spltty();

    if (dev == SND_DEV_AUDIO)
	d->flags &= ~SND_F_BUSY_AUDIO ;
    else if (dev == SND_DEV_DSP)
	d->flags &= ~SND_F_BUSY_DSP ;
    else if (dev == SND_DEV_DSP16)
	d->flags &= ~SND_F_BUSY_DSP16 ;
    if ( d->flags & SND_F_BUSY_ANY )	/* still some device open */
	splx(s);
    else {				/* last one */
	d->flags |= SND_F_CLOSING ;
	splx(s); /* is this ok here ? */
	snd_flush(d);
	outb(io_Status(d), 0);	/* Clear interrupt status */
	d->flags = 0 ;
    }
    return 0 ;
}

static int
mss_ioctl(dev_t dev, u_long cmd, caddr_t arg, int mode, struct proc * p)
{
    snddev_info *d;
    int unit;

    dev = minor(dev);
    unit = dev >> 4 ;
    d = &pcm_info[unit] ;
    /*
     * handle mixer calls first. Reads are in the default handler,
     * so do not bother about them.
     */
    if ( (cmd & MIXER_WRITE(0)) == MIXER_WRITE(0) ) {
        cmd &= 0xff ;
        if (cmd == SOUND_MIXER_RECSRC)
	    return mss_set_recsrc(d, *(int *)arg) ;
	else
	    return mss_mixer_set(d, cmd, *(int *)arg) ;
    }

    return ENOSYS ; /* fallback to the default ioctl handler */
}


/*
 * the callback routine to handle all dma ops etc.
 * With the exception of INIT, all other callbacks are invoked
 * with interrupts disabled.
 */

static int
mss_callback(snddev_info *d, int reason)
{
    u_char m;
    int retry, wr, cnt;

    DEB(printf("-- mss_callback reason 0x%03x\n", reason));
    wr = reason & SND_CB_WR ;
    reason &= SND_CB_REASON_MASK ;
    switch (reason) {
    case SND_CB_INIT : /* called with int enabled and no pending I/O */
	/*
	 * perform all necessary initializations for i/o
	 */
	d->rec_fmt = d->play_fmt ; /* no split format on the MSS */
	snd_set_blocksize(d);
	mss_reinit(d);
	reset_dbuf(& (d->dbuf_in), SND_CHAN_RD );
	reset_dbuf(& (d->dbuf_out), SND_CHAN_WR );
	return 1 ;
	break ;
    
    case SND_CB_START :
	cnt = wr ? d->dbuf_out.dl : d->dbuf_in.dl ;
	if (d->play_fmt == AFMT_S16_LE)
	    cnt /= 2;
	if (d->flags & SND_F_STEREO)
	    cnt /= 2;
	cnt-- ;

	DEB(printf("-- (re)start cnt %d\n", cnt));
	m = ad_read(d,9) ;
	DEB( if (m & 4) printf("OUCH! reg 9 0x%02x\n", m); );
	m |= wr ? I9_PEN : I9_CEN ; /* enable DMA */
	/*
	 * on the OPTi931 the enable bit seems hard to set...
	 */
	for (retry = 10; retry; retry--) {
	    ad_write(d, 9, m );
	    if (ad_read(d,9) ==m) break;
	}
	if (retry == 0)
	    printf("start dma, failed to set bit 0x%02x 0x%02x\n",
		m, ad_read(d, 9) ) ;
	if (wr || ! FULL_DUPLEX(d) )
	    ad_write_cnt(d, 14, cnt);
	else
	    ad_write_cnt(d, 30, cnt);

	break ;

    case SND_CB_STOP :
    case SND_CB_ABORT : /* XXX check this... */
	m = ad_read(d,9) ;
	m &= wr ?  ~I9_PEN : ~I9_CEN ; /* Stop DMA */
	/*
	 * on the OPTi931 the enable bit seems hard to set...
	 */
	for (retry = 10; retry ; retry-- ) {
	    ad_write(d, 9, m );
	    if (ad_read(d,9) ==m) break;
	}
	if (retry == 0)
	    printf("start dma, failed to clear bit 0x%02x 0x%02x\n",
		m, ad_read(d, 9) ) ;
#if 1
	/*
	 * try to disable DMA by clearing count registers. Not sure it
	 * is needed, and it might cause false interrupts when the
	 * DMA is re-enabled later.
	 */
	if (wr || ! FULL_DUPLEX(d) )
	    ad_write_cnt(d, 14, 0);
	else
	    ad_write_cnt(d, 30, 0);
	break;
#endif
    }
    return 0 ;
}

/*
 * main irq handler for the CS423x. The OPTi931 code is
 * a separate one.
 * The correct way to operate for a device with multiple internal
 * interrupt sources is to loop on the status register and ack
 * interrupts until all interrupts are served and none are reported. At
 * this point the IRQ line to the ISA IRQ controller should go low
 * and be raised at the next interrupt.
 *
 * Since the ISA IRQ controller is sent EOI _before_ passing control
 * to the isr, it might happen that we serve an interrupt early, in
 * which case the status register at the next interrupt should just
 * say that there are no more interrupts...
 */

static void
mss_intr(int unit)
{
    snddev_info *d = &pcm_info[unit];
    u_char c, served = 0;
    int i;

    DEB(printf("mss_intr\n"));
    ad_read(d, 11); /* fake read of status bits */

    /*
     * loop until there are interrupts, but no more than 10 times.
     */
    for (i=10 ; i && inb(io_Status(d)) & 1 ; i-- ) {
	/* get exact reason for full-duplex boards */
	c = FULL_DUPLEX(d) ? ad_read(d, 24) : 0x30 ;
	c &= ~served ;
	if ( d->dbuf_out.dl && (c & 0x10) ) {
	    served |= 0x10 ;
	    dsp_wrintr(d);
	}
	if ( d->dbuf_in.dl && (c & 0x20) ) {
	    served |= 0x20 ;
	    dsp_rdintr(d);
	}
	/* 
	 * now ack the interrupt
	 */
	if ( FULL_DUPLEX(d) )
	    ad_write(d, 24, ~c); /* ack selectively */
	else
	    outb(io_Status(d), 0);	/* Clear interrupt status */
    }
    if (served == 0) {
	printf("How strange... mss_intr with no reason!\n");
	/*
	 * this should not happen... I have no idea what to do now.
	 * maybe should do a sanity check and restart dmas ?
	 */
	outb(io_Status(d), 0);	/* Clear interrupt status */
    }
}

/*
 * the opti931 seems to miss interrupts when working in full
 * duplex, so we try some heuristics to catch them.
 */
static void
opti931_intr(int unit)
{
    snddev_info *d = &pcm_info[unit];
    u_char masked=0, i11, mc11, c=0;
    u_char reason; /* b0 = playback, b1 = capture, b2 = timer */
    int loops = 10;

#if 0
    reason = inb(io_Status(d));
    if ( ! (reason & 1) ) {/* no int, maybe a shared line ? */
	printf("opti931_intr: flag 0, mcir11 0x%02x\n", ad_read(d,11));
	return;
    }
#endif
    i11 = ad_read(d, 11); /* XXX what's for ? */
again:

    c=mc11 = FULL_DUPLEX(d) ? opti_read(d->conf_base, 11) : 0xc ;
    mc11 &= 0x0c ;
    if (c & 0x10) {
	DEB(printf("Warning: CD interrupt\n");)
	mc11 |= 0x10 ;
    }
    if (c & 0x20) {
	DEB(printf("Warning: MPU interrupt\n");)
	mc11 |= 0x20 ;
    }
    if (mc11 & masked) 
        printf("irq reset failed, mc11 0x%02x, masked 0x%02x\n", mc11, masked);
    masked |= mc11 ;
    /*
     * the nice OPTi931 sets the IRQ line before setting the bits in
     * mc11. So, on some occasions I have to retry (max 10 times).
     */
    if ( mc11 == 0 ) { /* perhaps can return ... */
	reason = inb(io_Status(d));
	if (reason & 1) {
	    DEB(printf("one more try...\n");)
	    if (--loops)
		goto again;
	    else
		DDB(printf("opti_intr: irq but mc11 not set!...\n");)
	}
	if (loops==10)
	    printf("ouch, intr but nothing in mcir11 0x%02x\n", mc11);
	return;
    }

    if ( d->dbuf_in.dl && (mc11 & 8) ) {
	dsp_rdintr(d);
    }
    if ( d->dbuf_out.dl && (mc11 & 4) ) {
	dsp_wrintr(d);
    }
    opti_write(d->conf_base, 11, ~mc11); /* ack */
    if (--loops)
	goto again;
    DEB(printf("xxx too many loops\n");)
}

/*
 * Second part of the file: functions local to this module.
 * in this section a few routines to access MSS registers
 *
 */

static void
opti_write(int io_base, u_char reg, u_char value)
{
    outb(io_base, reg);
    outb(io_base+1, value);
}

static u_char
opti_read(int io_base, u_char reg)
{
    outb(io_base, reg);
    return inb(io_base+1);
}

static void
gus_write(int io_base, u_char reg, u_char value)
{
    outb(io_base + 3, reg);
    outb(io_base + 5, value);
}

static void
gus_writew(int io_base, u_char reg, u_short value)
{
    outb(io_base + 3, reg);
    outb(io_base + 4, value);
}

static u_char
gus_read(int io_base, u_char reg)
{
    outb(io_base+3, reg);
    return inb(io_base+5);
}

static u_short
gus_readw(int io_base, u_char reg)
{
    outb(io_base+3, reg);
    return inw(io_base+4);
}


/*
 * AD_WAIT_INIT waits if we are initializing the board and
 * we cannot modify its settings
 */
static int
AD_WAIT_INIT(snddev_info *d, int x)
{
    int arg=x, n = 0; /* to shut up the compiler... */
    for (; x-- ; )
	if ( (n=inb(io_Index_Addr(d))) & IA_BUSY)
	    DELAY(10);
	else
	    return n ;
    printf("AD_WAIT_INIT FAILED %d 0x%02x\n", arg, n);
    return n ;
}

static int
ad_read(snddev_info *d, int reg)
{
    u_long   flags;
    int             x;

    flags = spltty();
    AD_WAIT_INIT(d, 201);
    x = inb(io_Index_Addr(d)) & ~IA_AMASK ;
    outb(io_Index_Addr(d), (u_char) (reg & IA_AMASK) | x ) ;
    x = inb(io_Indexed_Data(d));
    splx(flags);
    return x;
}

static void
ad_write(snddev_info *d, int reg, u_char data)
{
    u_long   flags;

    int x ;
    flags = spltty();
    AD_WAIT_INIT(d, 1002);
    x = inb(io_Index_Addr(d)) & ~IA_AMASK ;
    outb(io_Index_Addr(d), (u_char) (reg & IA_AMASK) | x ) ;
    outb(io_Indexed_Data(d), data);
    splx(flags);
}

static void
ad_write_cnt(snddev_info *d, int reg, u_short cnt)
{
    ad_write(d, reg+1, cnt & 0xff );
    ad_write(d, reg, cnt >> 8 ); /* upper base must be last */
}

static void
wait_for_calibration(snddev_info *d)
{
    int n, t;

    /*
     * Wait until the auto calibration process has finished.
     * 
     * 1) Wait until the chip becomes ready (reads don't return 0x80).
     * 2) Wait until the ACI bit of I11 gets on
     * 3) Wait until the ACI bit of I11 gets off
     */

    n = AD_WAIT_INIT(d, 1000);
    if (n & IA_BUSY)
	printf("mss: Auto calibration timed out(1).\n");

    for (t = 100 ; t>0 && (ad_read(d, 11) & 0x20) == 0 ; t--)
	    DELAY(100);
    for (t = 100 ; t>0 && ad_read(d, 11) & 0x20 ; t--)
	    DELAY(100);
}

#if 0 /* unused right now... */
static void
ad_mute(snddev_info *d)
{
    ad_write(d, 6, ad_read(d,6) | I6_MUTE);
    ad_write(d, 7, ad_read(d,7) | I6_MUTE);
}

static void
ad_unmute(snddev_info *d)
{
    ad_write(d, 6, ad_read(d,6) & ~I6_MUTE);
    ad_write(d, 7, ad_read(d,7) & ~I6_MUTE);
}
#endif

static void
ad_enter_MCE(snddev_info *d)
{
    int prev;

    d->bd_flags |= BD_F_MCE_BIT;
    AD_WAIT_INIT(d, 203);
    prev = inb(io_Index_Addr(d));
    prev &= ~IA_TRD ;
    outb(io_Index_Addr(d), prev | IA_MCE ) ;
}

static void
ad_leave_MCE(snddev_info *d)
{
    u_long   flags;
    u_char   prev;

    if ( (d->bd_flags & BD_F_MCE_BIT) == 0 ) {
	printf("--- hey, leave_MCE: MCE bit was not set!\n");
	return;
    }

    AD_WAIT_INIT(d, 1000);

    flags = spltty();
    d->bd_flags &= ~BD_F_MCE_BIT;

    prev = inb(io_Index_Addr(d));
    prev &= ~IA_TRD ;
    outb(io_Index_Addr(d), prev & ~IA_MCE ); /* Clear the MCE bit */
    wait_for_calibration(d);
    splx(flags);
}

/*
 * only one source can be set...
 */
static int
mss_set_recsrc(snddev_info *d, int mask)
{
    u_char   recdev;

    mask &= d->mix_rec_devs;
    switch (mask) {
    case SOUND_MASK_LINE:
    case SOUND_MASK_LINE3:
	recdev = 0;
	break;

    case SOUND_MASK_CD:
    case SOUND_MASK_LINE1:
	recdev = 0x40;
	break;

    case SOUND_MASK_IMIX:
	recdev = 0xc0;
	break;

    case SOUND_MASK_MIC:
    default:
	mask = SOUND_MASK_MIC;
	recdev = 0x80;
    }

    ad_write(d, 0, (ad_read(d, 0) & 0x3f) | recdev);
    ad_write(d, 1, (ad_read(d, 1) & 0x3f) | recdev);

    d->mix_recsrc = mask;
    return 0;
}

/*
 * mixer conversion table: from 0..100 scale to codec values
 *
 * I don't understand what's this for... maybe achieve a log-scale
 * volume control ?
 */

static char mix_cvt[101] = { 
     0, 1, 3, 7,10,13,16,19,21,23,26,28,30,32,34,35,37,39,40,42,
    43,45,46,47,49,50,51,52,53,55,56,57,58,59,60,61,62,63,64,65,
    65,66,67,68,69,70,70,71,72,73,73,74,75,75,76,77,77,78,79,79,
    80,81,81,82,82,83,84,84,85,85,86,86,87,87,88,88,89,89,90,90,
    91,91,92,92,93,93,94,94,95,95,96,96,96,97,97,98,98,98,99,99, 
    100
}; 

/*
 * there are differences in the mixer depending on the actual sound
 * card.
 */
static int
mss_mixer_set(snddev_info *d, int dev, int value)
{
    int             left = value & 0x000000ff;
    int             right = (value & 0x0000ff00) >> 8;

    int             regoffs;
    mixer_tab *mix_d = &mix_devices;

    u_char  old, val;

    if (dev > 31)
	return EINVAL;

    if (!(d->mix_devs & (1 << dev)))
	return EINVAL;

    if (d->bd_id == MD_OPTI931)
	mix_d = &(opti931_devices);

    if ((*mix_d)[dev][LEFT_CHN].nbits == 0) {
	DEB(printf("nbits = 0 for dev %d\n", dev) );
	return EINVAL;
    }

    if (left > 100)
	left = 100;
    if (right > 100)
	right = 100;


    if ( (*mix_d)[dev][RIGHT_CHN].nbits == 0)	/* Mono control */
	right = left;

    d->mix_levels[dev] = left | (right << 8);

#if 0
    /* Scale volumes */
    left = mix_cvt[left];
    right = mix_cvt[right];
#endif
    /*
     * Set the left channel
     */

    regoffs = (*mix_d)[dev][LEFT_CHN].regno;
    old = val = ad_read(d, regoffs);
    /*
     * if volume is 0, mute chan. Otherwise, unmute.
     */
    if (regoffs != 0)  /* main input is different */
	val = (left == 0 ) ? old | 0x80 : old & 0x7f ;

    change_bits(mix_d, &val, dev, LEFT_CHN, left);
    ad_write(d, regoffs, val);
    DEB(printf("LEFT: dev %d reg %d old 0x%02x new 0x%02x\n",
	dev, regoffs, old, val));

    if ((*mix_d)[dev][RIGHT_CHN].nbits != 0) { /* have stereo */
	/*
	 * Set the right channel
	 */
	regoffs = (*mix_d)[dev][RIGHT_CHN].regno;
	old = val = ad_read(d, regoffs);
	if (regoffs != 1)
	    val = (right == 0 ) ? old | 0x80 : old & 0x7f ;
	change_bits(mix_d, &val, dev, RIGHT_CHN, right);
	ad_write(d, regoffs, val);
	DEB(printf("RIGHT: dev %d reg %d old 0x%02x new 0x%02x\n",
	    dev, regoffs, old, val));
    }
    return 0; /* success */
}

static void
ad1848_mixer_reset(snddev_info *d)
{
    int             i;

    if (d->bd_id == MD_OPTI931)
	d->mix_devs = OPTI931_MIXER_DEVICES;
    else if (d->bd_id != MD_AD1848)
	d->mix_devs = MODE2_MIXER_DEVICES;
    else
	d->mix_devs = MODE1_MIXER_DEVICES;

    d->mix_rec_devs = MSS_REC_DEVICES;

    for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
	if (d->mix_devs & (1 << i))
	    mss_mixer_set(d, i, default_mixer_levels[i]);
    mss_set_recsrc(d, SOUND_MASK_MIC);
    /*
     * some device-specific things, mostly mute the mic to
     * the output mixer so as to avoid hisses. In many cases this
     * is the default after reset, this code is here mostly as a
     * reminder that this might be necessary on other boards.
     */
    switch(d->bd_id) {
    case MD_OPTI931:
	ad_write(d, 20, 0x88);
	ad_write(d, 21, 0x88);
	break;

    case MD_YM0020:
	/* set master volume to max */
	DDB(printf("set yamaha master volume to max"); )
	outb(0x370, 7) ;
	outb(0x371, 0) ;
	outb(0x370, 8) ;
	outb(0x371, 0) ;
	break;

    case MD_GUSPNP:
	/* this is only necessary in mode 3 ... */
	ad_write(d, 22, 0x88);
	ad_write(d, 23, 0x88);
    }
}

/*
 * mss_speed processes the value in play_speed finding the
 * matching one. As a side effect, it returns the value to
 * be written in the speed bits of the codec. It does _NOT_
 * set the speed of the device (but it should!)
 */

static int
mss_speed(snddev_info *d)
{
    /*
     * In the CS4231, the low 4 bits of I8 are used to hold the
     * sample rate.  Only a fixed number of values is allowed. This
     * table lists them. The speed-setting routines scans the table
     * looking for the closest match. This is the only supported method.
     *
     * In the CS4236, there is an alternate metod (which we do not
     * support yet) which provides almost arbitrary frequency setting.
     * In the AD1845, it looks like the sample rate can be
     * almost arbitrary, and written directly to a register.
     * In the OPTi931, there is a SB command which provides for
     * almost arbitrary frequency setting.
     *
     */
    static int speeds[] = {
	8000, 5512, 16000, 11025, 27429, 18900, 32000, 22050,
	-1, 37800, -1, 44100, 48000, 33075, 9600, 6615
    };

    int arg, i, sel = 0; /* assume entry 0 does not contain -1 */

    arg = d->play_speed ;

    for (i=1; i < 16 ; i++)
	if (speeds[i] >0 && abs(arg-speeds[i]) < abs(arg-speeds[sel]) )
	    sel = i ;

    d->play_speed = d->rec_speed = speeds[sel] ;
    return sel ;
}

/*
 * mss_format checks that the format is supported (or defaults to AFMT_U8)
 * and returns the bit setting for the 1848 register corresponding to
 * the desired format.
 *
 * fixed lr970724
 */

static int
mss_format(snddev_info *d)
{
    int i, arg = d->play_fmt ;

    /*
     * The data format uses 3 bits (just 2 on the 1848). For each
     * bit setting, the following array returns the corresponding format.
     * The code scans the array looking for a suitable format. In
     * case it is not found, default to AFMT_U8 (not such a good
     * choice, but let's do it for compatibility...).
     */

    static int fmts[] = {
	AFMT_U8, AFMT_MU_LAW, AFMT_S16_LE, AFMT_A_LAW,
	-1, AFMT_IMA_ADPCM, AFMT_U16_BE, -1
    };

    if ( (arg & d->audio_fmt) == 0 ) /* unsupported fmt, default to AFMT_U8 */
	arg = AFMT_U8 ;

    /* ulaw/alaw seems broken on the opti931... */
    if (d->bd_id == MD_OPTI931 || d->bd_id == MD_GUSPNP) {
	if (arg == AFMT_MU_LAW) {
	    arg = AFMT_U8 ;
	    d->flags |= SND_F_XLAT8 ;
	} else
	    d->flags &= ~SND_F_XLAT8 ;
    }
    /*
     * check that arg is one of the supported formats in d->format;
     * otherwise fallback to AFMT_U8
     */

    for (i=0 ; i<8 ; i++)
	if (arg == fmts[i]) break;
    if (i==8) {	/* not found, default to AFMT_U8 */
	arg = AFMT_U8 ;
	i = 0 ;
    }
    d->play_fmt = d->rec_fmt = arg;

    return i ;
}

/*
 * mss_detect can be used in the probe and the attach routine.
 *
 * We store probe information in pcm_info[unit]. This descriptor
 * is reinitialized just before the attach, so all relevant
 * information is lost, and mss_detect must be run again in
 * the attach routine if necessary.
 */

int
mss_detect(struct isa_device *dev)
{
    int             i;
    u_char   tmp, tmp1, tmp2 ;
    snddev_info *d = &(pcm_info[dev->id_unit]);
    char *name;

    d->io_base = dev->id_iobase;
    d->bd_flags |= BD_F_MCE_BIT ;
    if (d->bd_id != 0) {
	printf("preselected bd_id 0x%04x -- %s\n",
		d->bd_id, d->name ? d->name : "???");
	return 1;
    }

    name = "AD1848" ;
    d->bd_id = MD_AD1848; /* AD1848 or CS4248 */

    /*
     * Check that the I/O address is in use.
     * 
     * bit 7 of the base I/O port is known to be 0 after the chip has
     * performed its power on initialization. Just assume this has
     * happened before the OS is starting.
     * 
     * If the I/O address is unused, it typically returns 0xff.
     */

    for (i=0; i<10; i++)
	if (inb(io_Index_Addr(d)) & IA_BUSY)
	    DELAY(10000); /* maybe busy, wait & retry later */
	else
	    break ;
    if ((inb(io_Index_Addr(d)) & IA_BUSY) != 0x00) {	/* Not a AD1848 */
	BVDDB(printf("mss_detect error, busy still set (0x%02x)\n",
		   inb(io_Index_Addr(d))));
	return 0;
    }
    /*
     * Test if it's possible to change contents of the indirect
     * registers. Registers 0 and 1 are ADC volume registers. The bit
     * 0x10 is read only so try to avoid using it.
     */

    ad_write(d, 0, 0xaa);
    ad_write(d, 1, 0x45);/* 0x55 with bit 0x10 clear */
    tmp1 = ad_read(d, 0) ;
    tmp2 = ad_read(d, 1) ;
    if ( tmp1 != 0xaa || tmp2 != 0x45) {
	BVDDB(printf("mss_detect error - IREG (0x%02x/0x%02x) want 0xaa/0x45\n",
		tmp1, tmp2));
	return 0;
    }

    ad_write(d, 0, 0x45);
    ad_write(d, 1, 0xaa);
    tmp1 = ad_read(d, 0) ;
    tmp2 = ad_read(d, 1) ;

    if (tmp1 != 0x45 || tmp2 != 0xaa) {
	BVDDB(printf("mss_detect error - IREG2 (%x/%x)\n", tmp1, tmp2));
	return 0;
    }

    /*
     * The indirect register I12 has some read only bits. Lets try to
     * change them.
     */

    tmp = ad_read(d, 12);
    ad_write(d, 12, (~tmp) & 0x0f);
    tmp1 = ad_read(d, 12);

    if ((tmp & 0x0f) != (tmp1 & 0x0f)) {
	BVDDB(printf("mss_detect error - I12 (0x%02x was 0x%02x)\n",
	    tmp1, tmp));
	return 0;
    }

    /*
     * NOTE! Last 4 bits of the reg I12 tell the chip revision.
     *	0x01=RevB
     *  0x0A=RevC. also CS4231/CS4231A and OPTi931
     */

    BVDDB(printf("mss_detect - chip revision 0x%02x\n", tmp & 0x0f);)

    /*
     * The original AD1848/CS4248 has just 16 indirect registers. This
     * means that I0 and I16 should return the same value (etc.). Ensure
     * that the Mode2 enable bit of I12 is 0. Otherwise this test fails
     * with new parts.
     */

    ad_write(d, 12, 0);	/* Mode2=disabled */

    for (i = 0; i < 16; i++)
	if ((tmp1 = ad_read(d, i)) != (tmp2 = ad_read(d, i + 16))) {
	    BVDDB(printf("mss_detect warning - I%d: 0x%02x/0x%02x\n",
		i, tmp1, tmp2));
	    /*
	     * note - this seems to fail on the 4232 on I11. So we just break
	     * rather than fail.
	     */
	    break ; /* return 0; */
	}
    /*
     * Try to switch the chip to mode2 (CS4231) by setting the MODE2 bit
     * (0x40). The bit 0x80 is always 1 in CS4248 and CS4231.
     *
     * On the OPTi931, however, I12 is readonly and only contains the
     * chip revision ID (as in the CS4231A). The upper bits return 0.
     */

    ad_write(d, 12, 0x40);	/* Set mode2, clear 0x80 */

    tmp1 = ad_read(d, 12);
    if (tmp1 & 0x80) {
	name = "CS4248" ; /* Our best knowledge just now */
    }
    if ((tmp1 & 0xf0) == 0x00) {
	BVDDB(printf("this should be an OPTi931\n");)
    } else if ((tmp1 & 0xc0) == 0xC0) {
	/*
	 * The 4231 has bit7=1 always, and bit6 we just set to 1.
	 * We want to check that this is really a CS4231
	 * Verify that setting I0 doesn't change I16.
	 */
	ad_write(d, 16, 0);	/* Set I16 to known value */

	ad_write(d, 0, 0x45);
	if ((tmp1 = ad_read(d, 16)) != 0x45) { /* No change -> CS4231? */

	    ad_write(d, 0, 0xaa);
	    if ((tmp1 = ad_read(d, 16)) == 0xaa) {	/* Rotten bits? */
		BVDDB(printf("mss_detect error - step H(%x)\n", tmp1));
		return 0;
	    }
	    /*
	     * Verify that some bits of I25 are read only.
	     */

	    tmp1 = ad_read(d, 25);	/* Original bits */
	    ad_write(d, 25, ~tmp1);	/* Invert all bits */
	    if ((ad_read(d, 25) & 0xe7) == (tmp1 & 0xe7)) {
		int             id;

		/*
		 * It's at least CS4231
		 */
		name = "CS4231" ;
		d->bd_id = MD_CS4231;

		/*
		 * It could be an AD1845 or CS4231A as well.
		 * CS4231 and AD1845 report the same revision info in I25
		 * while the CS4231A reports different.
		 */

		id = ad_read(d, 25) & 0xe7;
		/*
		 * b7-b5 = version number;
		 *	100 : all CS4231
		 *	101 : CS4231A
		 *      
		 * b2-b0 = chip id;
		 */
		switch (id) {

		case 0xa0:
		    name = "CS4231A" ;
		    d->bd_id = MD_CS4231A;
		    break;

		case 0xa2:
		    name = "CS4232" ;
		    d->bd_id = MD_CS4232;
		    break;

		case 0xb2:
		    /* strange: the 4231 data sheet says b4-b3 are XX
		     * so this should be the same as 0xa2
		     */
		    name = "CS4232A" ;
		    d->bd_id = MD_CS4232A;
		    break;

		case 0x80:
		    /*
		     * It must be a CS4231 or AD1845. The register I23
		     * of CS4231 is undefined and it appears to be read
		     * only. AD1845 uses I23 for setting sample rate.
		     * Assume the chip is AD1845 if I23 is changeable.
		     */

		    tmp = ad_read(d, 23);

		    ad_write(d, 23, ~tmp);
		    if (ad_read(d, 23) != tmp) {	/* AD1845 ? */
			name = "AD1845" ;
			d->bd_id = MD_AD1845;
		    }
		    ad_write(d, 23, tmp);	/* Restore */
		    DDB(printf("... try to identify the yamaha\n") ;)
		    tmp = inb(0x370) ;
		    outb(0x370, 6 /* dma config */ ) ;
		    if (inb(0x370) != 6 ) /* not a yamaha... restore. */
			outb(0x370, tmp ) ;
		    else
			d->bd_id = MD_YM0020 ;
		    break;

		case 0x83:	/* CS4236 */
		case 0x03:      /* CS4236 on Intel PR440FX motherboard XXX */
		    name = "CS4236";
		    d->bd_id = MD_CS4236;
		    break ;

		default:	/* Assume CS4231 */
		    BVDDB(printf("unknown id 0x%02x, assuming CS4231\n", id);)
		    d->bd_id = MD_CS4231;
		}
	    }
	    ad_write(d, 25, tmp1);	/* Restore bits */

	}
    }
    BVDDB(printf("mss_detect() - Detected %s\n", name));
    strcpy(d->name, name);
    dev->id_flags &= ~DV_F_DEV_MASK ;
    dev->id_flags |= (d->bd_id << DV_F_DEV_SHIFT) & DV_F_DEV_MASK ;
    return 1;
}


/*
 * mss_reinit resets registers of the codec
 */
static void
mss_reinit(snddev_info *d)
{
    u_char r;

    r = mss_speed(d) ;
    r |= (mss_format(d) << 5) ;
    if (d->flags & SND_F_STEREO)
	r |= 0x10 ;
    /* XXX check if MCE is necessary... */
    ad_enter_MCE(d);

    /*
     * perhaps this is not the place to set mode2, should be done
     * only once at attach time...
     */
    if ( FULL_DUPLEX(d) && d->bd_id != MD_OPTI931)
	/*
	 * set mode2 bit for dual dma op. This bit is not implemented
	 * on the OPTi931
	 */
	ad_write(d, 12, ad_read(d, 12) | 0x40 /* mode 2 on the CS42xx */ );

    /*
     * XXX this should really go into mss-speed...
     */
    if (d->bd_id == MD_AD1845) { /* Use alternate speed select regs */
	r &= 0xf0;	/* Mask off the rate select bits */

	ad_write(d, 22, (d->play_speed >> 8) & 0xff);	/* Speed MSB */
	ad_write(d, 23, d->play_speed & 0xff);	/* Speed LSB */
	/*
	 * XXX must also do something in I27 for the ad1845
	 */
    }

    ad_write(d, 8, r) ;
    if ( FULL_DUPLEX(d) ) {
#if 0
	if (d->bd_id == MD_GUSPNP && d->play_fmt == AFMT_MU_LAW) {
	    printf("warning, cannot do ulaw rec + play on the GUS\n");
	    r = 0 ; /* move to U8 */
	}
#endif
	ad_write(d, 28, r & 0xf0 ) ; /* capture mode */
	ad_write(d, 9, 0 /* no capture, no playback, dual dma */) ;
    } else
	ad_write(d, 9, 4 /* no capture, no playback, single dma */) ;
    ad_leave_MCE(d);
    /*
     * not sure if this is really needed...
     */
    ad_write_cnt(d, 14, 0 ); /* playback count */
    if ( FULL_DUPLEX(d) )
	ad_write_cnt(d, 30, 0 ); /* rec. count on dual dma */

    ad_write(d, 10, 2 /* int enable */) ;
    outb(io_Status(d), 0);	/* Clear interrupt status */
    /* the following seem required on the CS4232 */
    ad_write(d, 6, ad_read(d,6) & ~I6_MUTE);
    ad_write(d, 7, ad_read(d,7) & ~I6_MUTE);

    snd_set_blocksize(d); /* update blocksize if user did not force it */
}

/*
 * here we have support for PnP cards
 *
 */

#if NPNP > 0

static char * cs423x_probe(u_long csn, u_long vend_id);
static void cs423x_attach(u_long csn, u_long vend_id, char *name,
	struct isa_device *dev);

static struct pnp_device cs423x = {
	"CS423x/Yamaha",
	cs423x_probe,
	cs423x_attach,
	&nsnd,	/* use this for all sound cards */
	&tty_imask	/* imask */
};
DATA_SET (pnpdevice_set, cs423x);

static char *
cs423x_probe(u_long csn, u_long vend_id)
{
    char *s = NULL ;
    u_long id = vend_id & 0xff00ffff;
    if ( id == 0x3700630e )
	s = "CS4237" ;
    if ( id == 0x2500630e )
	s = "CS4235" ;
    else if ( id == 0x3500630e || id == 0x3600630e )
	s = "CS4236" ;
    else if ( id == 0x3500630e )
	s = "CS4236B" ;
    else if ( id == 0x3200630e)
	s = "CS4232" ;
    else if ( id == 0x2000a865)
	s = "Yamaha SA2";
    else if ( id == 0x3000a865)
	s = "Yamaha SA3";
    else if (vend_id == 0x0000a865)
	s = "Yamaha YMF719 OPL-SA3";
    else if (vend_id == 0x8140d315)
	s = "SoundscapeVIVO";
    if (s) {
	struct pnp_cinfo d;
	read_pnp_parms(&d, 0);
	if (d.enable == 0) {
	    printf("This is a %s, but LDN 0 is disabled\n", s);
	    return NULL ;
	}
	return s;
    }

    return NULL ;
}

extern snddev_info sb_op_desc;

static void
cs423x_attach(u_long csn, u_long vend_id, char *name,
	struct isa_device *dev)
{
    struct pnp_cinfo d ;
    snddev_info tmp_d ; /* patched copy of the basic snddev_info */
    int ldn = 0 ;

    if (read_pnp_parms ( &d , ldn ) == 0 ) {
	printf("failed to read pnp parms\n");
	return ;
    }
    snddev_last_probed = &tmp_d;
    if (d.flags & DV_PNP_SBCODEC) {	/*** use sb-compatible codec ***/
	dev->id_alive = 16 ; /* number of io ports ? */
	tmp_d = sb_op_desc ;
	if (vend_id==0x2000a865 || vend_id==0x3000a865 ||
	    vend_id==0x0008a865 || vend_id==0x8140d315) {
	    /* Yamaha SA2/SA3 or ENSONIQ SoundscapeVIVO ENS4081 */
	    dev->id_iobase = d.port[0] ;
	    tmp_d.alt_base = d.port[1] ;
	    d.irq[1] = 0 ; /* only needed for the VIVO */
	} else {
	    dev->id_iobase = d.port[2] ;
	    tmp_d.alt_base = d.port[0] - 4;
	}
	d.drq[1] = 4 ; /* disable, it is not used ... */
    } else {			/* mss-compatible codec */
	dev->id_alive = 8 ; /* number of io ports ? */
	tmp_d = mss_op_desc ;
	dev->id_iobase = d.port[0] -4 ; /* XXX old mss have 4 bytes before... */
	tmp_d.alt_base = d.port[2];
	switch (vend_id & 0xff00ffff) {

	case 0x2000a865:	/* Yamaha SA2 */
	case 0x3000a865:	/* Yamaha SA3 */
	case 0x0000a865:	/* Yamaha TMF719 SA3 */
	    dev->id_iobase = d.port[1];
	    tmp_d.alt_base = d.port[0];
	    tmp_d.conf_base = d.port[4];
	    tmp_d.bd_id = MD_YM0020 ;
	    break;

	case 0x8100d315:	/* ENSONIQ SoundscapeVIVO */
	    dev->id_iobase = d.port[1];
	    tmp_d.alt_base = d.port[0];
	    tmp_d.bd_id = MD_VIVO ;
	    d.irq[1] = 0 ;
	    break;

	case 0x3700630e:        /* CS4237 */
	    tmp_d.bd_id = MD_CS4237 ;
	    break;

	case 0x2500630e:	/* AOpen AW37 */
	    tmp_d.bd_id = MD_CS4237 ;
	    break ;

	case 0x3500630e:        /* CS4236B */
	case 0x3600630e:        /* CS4236 */
	    tmp_d.bd_id = MD_CS4236 ;
	    break;

        default:
	    tmp_d.bd_id = MD_CS4232 ; /* to short-circuit the detect routine */
	    break;
	}
	strcpy(tmp_d.name, name);
	tmp_d.audio_fmt |= AFMT_FULLDUPLEX ;
    }
    write_pnp_parms( &d, ldn );
    enable_pnp_card();

    if ( (vend_id & 0x0000ffff) == 0x0000a865 ) {
	/* special volume setting for the Yamaha... */
	outb(tmp_d.conf_base, 7 /* volume, left */);
	outb(tmp_d.conf_base+1, 0 );
	outb(tmp_d.conf_base, 8 /* volume, right */);
	outb(tmp_d.conf_base+1, 0 );
    }
    dev->id_drq = d.drq[0] ; /* primary dma */
    dev->id_irq = (1 << d.irq[0] ) ;
    dev->id_intr = pcmintr ;
    dev->id_flags = DV_F_DUAL_DMA | (d.drq[1] ) ;

    tmp_d.synth_base = d.port[1]; /* XXX check this for yamaha */
    pcmattach(dev);
}

static char *opti931_probe(u_long csn, u_long vend_id);
static void opti931_attach(u_long csn, u_long vend_id, char *name,
	struct isa_device *dev);
static struct pnp_device opti931 = {
	"OPTi931",
	opti931_probe,
	opti931_attach,
	&nsnd,	/* use this for all sound cards */
	&tty_imask	/* imask */
};
DATA_SET (pnpdevice_set, opti931);

static char *
opti931_probe(u_long csn, u_long vend_id)
{
    if (vend_id == 0x3109143e) {
	struct pnp_cinfo d;
	read_pnp_parms(&d, 1);
	if (d.enable == 0) {
	    printf("This is an OPTi931, but LDN 1 is disabled\n");
	    return NULL ;
	}
	return "OPTi931" ;
    }
    return NULL ;
}

static void
opti931_attach(u_long csn, u_long vend_id, char *name,
	struct isa_device *dev)
{
    struct pnp_cinfo d ;
    snddev_info tmp_d ; /* patched copy of the basic snddev_info */
    int p;

    read_pnp_parms ( &d , 3 ); /* free resources taken by LDN 3 */
    d.irq[0]=0; /* free irq... */
    d.port[0]=0; /* free address... */
    d.enable = 0 ;
    write_pnp_parms ( &d , 3 );

    read_pnp_parms ( &d , 2 ); /* disable LDN 2 */
    d.enable = 0 ;
    write_pnp_parms ( &d , 2 );

    read_pnp_parms ( &d , 1 ) ;
    write_pnp_parms( &d, 1 );
    enable_pnp_card();

    snddev_last_probed = &tmp_d;
    tmp_d =  d.flags & DV_PNP_SBCODEC ? sb_op_desc : mss_op_desc ;

    strcpy(tmp_d.name, name);

    /*
     * My MED3931 v.1.0 allocates 3 bytes for the config space,
     * whereas v.2.0 allocates 4 bytes. What I know for sure is that the
     * upper two ports must be used, and they should end on a boundary
     * of 4 bytes. So I need the following trick...
     */
    p = tmp_d.conf_base = (d.port[3] & ~3) + 2; /* config port */

    /*
     * now set default values for both modes.
     */
    dev->id_iobase = d.port[0] - 4 ; /* old mss have 4 bytes before... */
    tmp_d.io_base = dev->id_iobase; /* needed for ad_write to work... */
    tmp_d.alt_base = d.port[2];
    tmp_d.synth_base = d.port[1];
    opti_write(p, 4, 0xd6 /* fifo empty, OPL3, audio enable, SB3.2 */ );
    ad_write (&tmp_d, 10, 2); /* enable interrupts */

    if (d.flags & DV_PNP_SBCODEC) { /* sb-compatible codec */
	/*
	 * the 931 is not a real SB, it has important pieces of
	 * hardware controlled by both the MSS and the SB port...
	 */
	printf("--- opti931 in sb mode ---\n");
	opti_write(p, 6, 1); /* MCIR6 mss disable, sb enable */
	/*
	 * swap the main and alternate iobase address since we want
	 * to work in sb mode.
	 */
	dev->id_iobase = d.port[2] ;
	tmp_d.alt_base = d.port[0] - 4;
	dev->id_flags = DV_F_DUAL_DMA | d.drq[1] ;
    } else { /* mss-compatible codec */
	tmp_d.bd_id = MD_OPTI931 ; /* to short-circuit the detect routine */
	opti_write(p, 6 , 2);  /* MCIR6: mss enable, sb disable */
	opti_write(p, 5, 0x28);  /* MCIR5: codec in exp. mode,fifo */
	dev->id_flags = DV_F_DUAL_DMA | d.drq[1] ;
	tmp_d.audio_fmt |= AFMT_FULLDUPLEX ; /* not really well... */
	tmp_d.isr = opti931_intr;
    }
    dev->id_drq = d.drq[0] ; /* primary dma */
    dev->id_irq = (1 << d.irq[0] ) ;
    dev->id_intr = pcmintr ;
    pcmattach(dev);
}

static char *opti925_probe(u_long csn, u_long vend_id);
static void opti925_attach(u_long csn, u_long vend_id, char *name,
        struct isa_device *dev);
    
static struct pnp_device opti925 = {
        "opti925",
        opti925_probe,
        opti925_attach,
        &nsnd,  /* use this for all sound cards */
        &tty_imask      /* imask */ 
};  
DATA_SET (pnpdevice_set, opti925);
    
static char *
opti925_probe(u_long csn, u_long vend_id)
{
    if (vend_id == 0x2509143e) {
        struct pnp_cinfo d ;
        read_pnp_parms ( &d , 1 ) ;
        if (d.enable == 0) {
            printf("This is an OPTi925, but LDN 1 is disabled\n");
            return NULL;
        }
        return "OPTi925" ;
    }
    return NULL ;
}  

static void 
opti925_attach(u_long csn, u_long vend_id, char *name, 
        struct isa_device *dev) 
{
    struct pnp_cinfo d ; 
    snddev_info tmp_d ; /* patched copy of the basic snddev_info */
    int the_irq = 0 ; 

    tmp_d = mss_op_desc;
    snddev_last_probed = &tmp_d; 
  
    read_pnp_parms ( &d , 3 );  /* disable LDN 3 */
    the_irq = d.irq[0];
    d.port[0] = 0 ;
    d.enable = 0 ;
    write_pnp_parms ( &d , 3 );

    read_pnp_parms ( &d , 2 ); /* disable LDN 2 */
    d.port[0] = 0 ;
    d.enable = 0 ; 
    write_pnp_parms ( &d , 2 );

    read_pnp_parms ( &d , 1 ) ;
    d.irq[0] = the_irq ;
    dev->id_iobase = d.port[1];
    tmp_d.alt_base = d.port[0];  
    write_pnp_parms ( &d , 1 );
    enable_pnp_card();

    tmp_d.conf_base = d.port[3];

    dev->id_drq = d.drq[0] ; /* primary dma */
    dev->id_irq = (1 << d.irq[0] ) ;
    dev->id_intr = pcmintr ;
    dev->id_flags = DV_F_DUAL_DMA | d.drq[1] ;
    tmp_d.audio_fmt |= AFMT_FULLDUPLEX ;

    snddev_last_probed->probe(dev); /* not really necessary but doesn't harm */
 
    pcmattach(dev);
}

static void gus_mem_cfg(snddev_info *tmp);

static char *guspnp_probe(u_long csn, u_long vend_id);
static void guspnp_attach(u_long csn, u_long vend_id, char *name,
	struct isa_device *dev);
static struct pnp_device guspnp = {
	"GusPnP",
	guspnp_probe,
	guspnp_attach,
	&nsnd,	/* use this for all sound cards */
	&tty_imask	/* imask */
};
DATA_SET (pnpdevice_set, guspnp);

static char *
guspnp_probe(u_long csn, u_long vend_id)
{
    if (vend_id == 0x0100561e) {
	struct pnp_cinfo d;
	read_pnp_parms(&d, 0);
	if (d.enable == 0) {
	    printf("This is a GusPnP, but LDN 0 is disabled\n");
	    return NULL ;
	}
	return "GusPnP" ;
    }
    return NULL ;
}

static void
guspnp_attach(u_long csn, u_long vend_id, char *name,
	struct isa_device *dev)
{
    struct pnp_cinfo d ;
    snddev_info tmp_d ; /* patched copy of the basic snddev_info */

    u_char tmp;

    read_pnp_parms ( &d , 0 ) ;

    /* d.irq[1] = d.irq[0] ; */
    pnp_write ( 0xf2, 0xff ); /* enable power on the guspnp */

    write_pnp_parms ( &d , 0 );
    enable_pnp_card();

    tmp_d = mss_op_desc ;
    snddev_last_probed = &tmp_d;

    dev->id_iobase = d.port[2] - 4 ; /* room for 4 mss registers */
    dev->id_drq = d.drq[1] ; /* XXX PLAY dma */
    dev->id_irq = (1 << d.irq[0] ) ;
    dev->id_intr = pcmintr ;
    dev->id_flags = DV_F_DUAL_DMA | d.drq[0]  ; /* REC dma */

    tmp_d.io_base = d.port[2] - 4;
    tmp_d.alt_base = d.port[0]; /* 0x220 */
    tmp_d.conf_base = d.port[1];  /* gus control block... */
    tmp_d.bd_id = MD_GUSPNP ;

    /* reset */
    gus_write(tmp_d.conf_base, 0x4c /* _URSTI */, 0 );/* Pull reset */
    DELAY(1000 * 30);
    /* release reset  and enable DAC */
    gus_write(tmp_d.conf_base, 0x4c /* _URSTI */, 3 );
    DELAY(1000 * 30);
    /* end of reset */

    outb( tmp_d.alt_base, 0xC ); /* enable int and dma */

    /*
     * unmute left & right line. Need to go in mode3, unmute,
     * and back to mode 2
     */
    tmp = ad_read(&tmp_d, 0x0c);
    ad_write(&tmp_d, 0x0c, 0x6c ); /* special value to enter mode 3 */
    ad_write(&tmp_d, 0x19, 0 ); /* unmute left */
    ad_write(&tmp_d, 0x1b, 0 ); /* unmute right */
    ad_write(&tmp_d, 0x0c, tmp ); /* restore old mode */

    /* send codec interrupts on irq1 and only use that one */
    gus_write(tmp_d.conf_base, 0x5a , 0x4f );

    /* enable access to hidden regs */
    tmp = gus_read(tmp_d.conf_base, 0x5b /* IVERI */ );
    gus_write(tmp_d.conf_base, 0x5b , tmp | 1 );
    BVDDB(printf("GUS: silicon rev %c\n", 'A' + ( ( tmp & 0xf ) >> 4) );)

    strcpy(tmp_d.name, name);

    pcmattach(dev);
}

#if 0
int
gus_mem_write(snddev_info *d, int addr, u_char data)
{
    gus_writew(d->conf_base, 0x43 , addr & 0xffff );
    gus_write(d->conf_base, 0x44 , (addr>>16) & 0xff );
    outb(d->conf_base + 7, data);
}

u_char
gus_mem_read(snddev_info *d, int addr)
{
    gus_writew(d->conf_base, 0x43 , addr & 0xffff );
    gus_write(d->conf_base, 0x44 , (addr>>16) & 0xff );
    return inb(d->conf_base + 7);
}

void
gus_mem_cfg(snddev_info *d)
{
    int base;
    u_char old;
    u_char a, b;

    printf("configuring gus memory...\n");
    gus_writew(d->conf_base, 0x52 /* LMCFI */, 1 /* 512K*/);
    old = gus_read(d->conf_base, 0x19);
    gus_write(d->conf_base, 0x19, old | 1); /* enable enhaced mode */
    for (base = 0; base < 1024; base++) {
	a=gus_mem_read(d, base*1024);
	a = ~a ;
	gus_mem_write(d, base*1024, a);
	b=gus_mem_read(d, base*1024);
	if ( b != a )
		break ;
    }
    printf("Have found %d KB ( 0x%x != 0x%x)\n", base, a, b);
}
#endif /* gus mem cfg... */

#endif	/* NPNP > 0 */
#endif /* NPCM > 0 */
