root/sys/dev/isa/ad1848.c
/*      $OpenBSD: ad1848.c,v 1.50 2024/05/28 09:27:08 jsg Exp $ */
/*      $NetBSD: ad1848.c,v 1.45 1998/01/30 02:02:38 augustss Exp $     */

/*
 * Copyright (c) 1994 John Brezak
 * Copyright (c) 1991-1993 Regents of the University of California.
 * All rights reserved.
 *
 * 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the Computer Systems
 *      Engineering Group at Lawrence Berkeley Laboratory.
 * 4. Neither the name of the University nor of the Laboratory may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
 *
 */

/*
 * Copyright by Hannu Savolainen 1994
 *
 * 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.
 *
 */
/*
 * Portions of this code are from the VOXware support for the ad1848
 * by Hannu Savolainen <hannu@voxware.pp.fi>
 * 
 * Portions also supplied from the SoundBlaster driver for NetBSD.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/syslog.h>
#include <sys/device.h>
#include <sys/buf.h>
#include <sys/fcntl.h>

#include <machine/cpu.h>
#include <machine/bus.h>

#include <sys/audioio.h>

#include <dev/audio_if.h>

#include <dev/isa/isavar.h>
#include <dev/isa/isadmavar.h>

#include <dev/ic/ad1848reg.h>
#include <dev/ic/cs4231reg.h>
#include <dev/isa/ad1848var.h>

#ifdef AUDIO_DEBUG
#define DPRINTF(x)      do { if (ad1848debug) printf x; } while (0);
int     ad1848debug = 0;
#else
#define DPRINTF(x)
#endif

/*
 * Initial values for the indirect registers of CS4248/AD1848.
 */
static int ad1848_init_values[] = {
        GAIN_12 | INPUT_MIC_GAIN_ENABLE,        /* Left Input Control */
        GAIN_12 | INPUT_MIC_GAIN_ENABLE,        /* Right Input Control */
        ATTEN_12,                       /* Left Aux #1 Input Control */
        ATTEN_12,                       /* Right Aux #1 Input Control */
        ATTEN_12,                       /* Left Aux #2 Input Control */
        ATTEN_12,                       /* Right Aux #2 Input Control */
        /* bits 5-0 are attenuation select */
        ATTEN_12,                       /* Left DAC output Control */
        ATTEN_12,                       /* Right DAC output Control */
        CLOCK_XTAL1 | FMT_PCM8,         /* Clock and Data Format */
        SINGLE_DMA | AUTO_CAL_ENABLE,   /* Interface Config */
        INTERRUPT_ENABLE,               /* Pin control */
        0x00,                           /* Test and Init */
        MODE2,                          /* Misc control */
        ATTEN_0 << 2,                   /* Digital Mix Control */
        0,                              /* Upper base Count */
        0,                              /* Lower base Count */

        /* These are for CS4231 &c. only (additional registers): */
        0,                              /* Alt feature 1 */
        0,                              /* Alt feature 2 */
        ATTEN_12,                       /* Left line in */
        ATTEN_12,                       /* Right line in */
        0,                              /* Timer low */
        0,                              /* Timer high */
        0,                              /* unused */
        0,                              /* unused */
        0,                              /* IRQ status */
        0,                              /* unused */

        /* Mono input (a.k.a speaker) (mic) Control */
        MONO_INPUT_MUTE|ATTEN_6,        /* mute speaker by default */
        0,                              /* unused */
        0,                              /* record format */
        0,                              /* Crystal Clock Select */
        0,                              /* upper record count */
        0                               /* lower record count */
};

static struct audio_params ad1848_audio_default =
        {48000, AUDIO_ENCODING_SLINEAR_LE, 16, 2, 1, 2};

void    ad1848_reset(struct ad1848_softc *);
int     ad1848_set_speed(struct ad1848_softc *, u_long *);
void    ad1848_mute_monitor(void *, int);

/* indirect register access */
static int ad_read(struct ad1848_softc *, int);
static void ad_write(struct ad1848_softc *, int, int);
static void ad_set_MCE(struct ad1848_softc *, int);
static void wait_for_calibration(struct ad1848_softc *);

/* direct register (AD1848_{IADDR,IDATA,STATUS} only) access */
#define ADREAD(sc, addr) bus_space_read_1((sc)->sc_iot, (sc)->sc_ioh, (sc)->sc_iooffs+(addr))
#define ADWRITE(sc, addr, data) bus_space_write_1((sc)->sc_iot, (sc)->sc_ioh, (sc)->sc_iooffs+(addr), (data))

static int
ad_read(struct ad1848_softc *sc, int reg)
{
        int x;

        ADWRITE(sc, AD1848_IADDR, (reg & 0xff) | sc->MCE_bit);
        x = ADREAD(sc, AD1848_IDATA);
        /*  printf("(%02x<-%02x) ", reg|sc->MCE_bit, x); */

        return x;
}

static void
ad_write(struct ad1848_softc *sc, int reg, int data)
{
        ADWRITE(sc, AD1848_IADDR, (reg & 0xff) | sc->MCE_bit);
        ADWRITE(sc, AD1848_IDATA, data & 0xff);
        /* printf("(%02x->%02x) ", reg|sc->MCE_bit, data); */
}

static void
ad_set_MCE(struct ad1848_softc *sc, int state)
{
        if (state)
                sc->MCE_bit = MODE_CHANGE_ENABLE;
        else
                sc->MCE_bit = 0;

        ADWRITE(sc, AD1848_IADDR, sc->MCE_bit);
}

static void
wait_for_calibration(struct ad1848_softc *sc)
{
        int timeout;

        DPRINTF(("ad1848: Auto calibration started.\n"));
        /*
         * Wait until the auto calibration process has finished.
         *
         * 1) Wait until the chip becomes ready (reads don't return SP_IN_INIT).
         * 2) Wait until the ACI bit of I11 goes hi and then lo.
         *   a) With AD1848 alike, ACI goes hi within 5 sample cycles
         *        and remains hi for ~384 sample periods.
         *   b) With CS4231 alike, ACI goes hi immediately and remains
         *        hi for at least 168 sample periods.
         */
        timeout = AD1848_TIMO;
        while (timeout > 0 && ADREAD(sc, AD1848_IADDR) == SP_IN_INIT)
                timeout--;

        if (ADREAD(sc, AD1848_IADDR) == SP_IN_INIT)
                DPRINTF(("ad1848: Auto calibration timed out(1).\n"));

        if (!(sc->sc_flags & AD1848_FLAG_32REGS)) {
                timeout = AD1848_TIMO;
                while (timeout > 0 &&
                    !(ad_read(sc, SP_TEST_AND_INIT) & AUTO_CAL_IN_PROG))
                        timeout--;

                if (!(ad_read(sc, SP_TEST_AND_INIT) & AUTO_CAL_IN_PROG)) {
                        DPRINTF(("ad1848: Auto calibration timed out(2).\n"));
                }
        }

        timeout = AD1848_TIMO;
        while (timeout > 0 && ad_read(sc, SP_TEST_AND_INIT) & AUTO_CAL_IN_PROG)
                timeout--;
        if (ad_read(sc, SP_TEST_AND_INIT) & AUTO_CAL_IN_PROG)
                DPRINTF(("ad1848: Auto calibration timed out(3).\n"));
}

#ifdef AUDIO_DEBUG
void ad1848_dump_regs(struct ad1848_softc *);

void
ad1848_dump_regs(struct ad1848_softc *sc)
{
        int i;
        u_char r;
        
        printf("ad1848 status=%02x", ADREAD(sc, AD1848_STATUS));
        printf(" regs: ");
        for (i = 0; i < 16; i++) {
                r = ad_read(sc, i);
                printf("%02x ", r);
        }
        if (sc->mode == 2) {
                for (i = 16; i < 32; i++) {
                        r = ad_read(sc, i);
                        printf("%02x ", r);
                }
        }
        printf("\n");
}
#endif

/*
 * Map and probe for the ad1848 chip
 */
int
ad1848_mapprobe(struct ad1848_softc *sc, int iobase)
{
        if (!AD1848_BASE_VALID(iobase)) {
#ifdef AUDIO_DEBUG
                printf("ad1848: configured iobase %04x invalid\n", iobase);
#endif
                return 0;
        }

        sc->sc_iooffs = 0;
        /* Map the AD1848 ports */
        if (bus_space_map(sc->sc_iot, iobase, AD1848_NPORT, 0, &sc->sc_ioh))
                return 0;

        if (!ad1848_probe(sc)) {
                bus_space_unmap(sc->sc_iot, sc->sc_ioh, AD1848_NPORT);
                return 0;
        } else
                return 1;
}

/*
 * Probe for the ad1848 chip
 */
int
ad1848_probe(struct ad1848_softc *sc)
{
        u_char tmp, tmp1 = 0xff, tmp2 = 0xff;
#if 0
        int i;
#endif

        /* Is there an ad1848 chip ? */
        sc->MCE_bit = MODE_CHANGE_ENABLE;
        sc->mode = 1;   /* MODE 1 = original ad1848/ad1846/cs4248 */
        sc->sc_flags = 0;

        /*
         * Check that the I/O address is in use.
         *
         * The SP_IN_INIT bit 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, inb() typically returns 0xff.
         */
        tmp = ADREAD(sc, AD1848_IADDR);
        if (tmp & SP_IN_INIT) { /* Not a AD1848 */
#if 0
                DPRINTF(("ad_detect_A %x\n", tmp));
#endif
                goto bad;
        }

        /*
         * 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(sc, 0, 0xaa);
        ad_write(sc, 1, 0x45);  /* 0x55 with bit 0x10 clear */

        if ((tmp1 = ad_read(sc, 0)) != 0xaa ||
            (tmp2 = ad_read(sc, 1)) != 0x45) {
                DPRINTF(("ad_detect_B (%x/%x)\n", tmp1, tmp2));
                goto bad;
        }

        ad_write(sc, 0, 0x45);
        ad_write(sc, 1, 0xaa);

        if ((tmp1 = ad_read(sc, 0)) != 0x45 ||
            (tmp2 = ad_read(sc, 1)) != 0xaa) {
                DPRINTF(("ad_detect_C (%x/%x)\n", tmp1, tmp2));
                goto bad;
        }

        /*
         * The indirect register I12 has some read only bits. Lets
         * try to change them.
         */
        tmp = ad_read(sc, SP_MISC_INFO);
        ad_write(sc, SP_MISC_INFO, (~tmp) & 0x0f);

        if ((tmp & 0x0f) != ((tmp1 = ad_read(sc, SP_MISC_INFO)) & 0x0f)) {
                DPRINTF(("ad_detect_D (%x)\n", tmp1));
                goto bad;
        }

        /*
         * MSB and 4 LSBs of the reg I12 tell the chip revision.
         *
         * A preliminary version of the AD1846 data sheet stated that it
         * used an ID field of 0x0B.  The current version, however,
         * states that the AD1846 uses ID 0x0A, just like the AD1848K.
         *
         * this switch statement will need updating as newer clones arrive....
         */
        switch (tmp1 & 0x8f) {
        case 0x09:
                sc->chip_name = "AD1848J";
                break;
        case 0x0A:
                sc->chip_name = "AD1848K";
                break;
#if 0   /* See above */
        case 0x0B:
                sc->chip_name = "AD1846";
                break;
#endif
        case 0x81:
                sc->chip_name = "CS4248revB"; /* or CS4231 rev B; see below */
                break;
        case 0x89:
                sc->chip_name = "CS4248";
                break;
        case 0x8A:
                sc->chip_name = "broken"; /* CS4231/AD1845; see below */
                break;
        default:
                sc->chip_name = "unknown";
                DPRINTF(("ad1848: unknown codec version %#02X\n", (tmp1 & 0x8f)));
        }       

#if 0
        /*
         * XXX I don't know why, but this probe fails on an otherwise
         * well-working AW35/pro card, so I'll just take it out for now.
         * [niklas@openbsd.org]
         */

        /*
         * 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 CS4231, AD1845, etc.
         */
        ad_write(sc, SP_MISC_INFO, 0);  /* Mode2 = disabled */

        for (i = 0; i < 16; i++) {
                if ((tmp1 = ad_read(sc, i)) != (tmp2 = ad_read(sc, i + 16))) {
                        if (i != SP_TEST_AND_INIT) {
                                DPRINTF(("ad_detect_F(%d/%x/%x)\n", i, tmp1, tmp2));
                                goto bad;
                        }
                }
        }
#endif

        /*
         * Try to switch the chip to mode2 (CS4231) by setting the MODE2 bit
         * The bit 0x80 is always 1 in CS4248, CS4231, and AD1845.
         */
        ad_write(sc, SP_MISC_INFO, MODE2);      /* Set mode2, clear 0x80 */

        tmp1 = ad_read(sc, SP_MISC_INFO);
        if ((tmp1 & 0xc0) == (0x80 | MODE2)) {
                /*
                 *      CS4231 or AD1845 detected - is it?
                 *
                 *      Verify that setting I2 doesn't change I18.
                 */
                ad_write(sc, 18, 0x88); /* Set I18 to known value */

                ad_write(sc, 2, 0x45);
                if ((tmp2 = ad_read(sc, 18)) != 0x45) {
                        /* No change -> CS4231? */
                        ad_write(sc, 2, 0xaa);
                        if ((tmp2 = ad_read(sc, 18)) == 0xaa) {
                                /* Rotten bits? */
                                DPRINTF(("ad_detect_H(%x)\n", tmp2));
                                goto bad;
                        }

                        /*
                         *  It's a CS4231, or another clone with 32 registers.
                         *  Let's find out which by checking I25.
                         */
                        if ((tmp1 & 0x8f) == 0x8a) {
                                tmp1 = ad_read(sc, CS_VERSION_ID);
                                switch (tmp1 & 0xe7) {
                                case 0xA0:
                                        sc->chip_name = "CS4231A";
                                        break;
                                case 0x80:
                                        /* I25 no good, AD1845 same as CS4231 */
                                        sc->chip_name = "CS4231 or AD1845";
                                        break;
                                case 0x82:
                                        sc->chip_name = "CS4232";
                                        break;
                                case 0xa2:
                                        sc->chip_name = "CS4232C";
                                        break;
                                case 0x03:
                                        sc->chip_name = "CS4236/CS4236B";
                                        break;
                                }
                        }
                        sc->mode = 2;
                        sc->sc_flags |= AD1848_FLAG_32REGS;
                }
        }

        /* Wait for 1848 to init */
        while(ADREAD(sc, AD1848_IADDR) & SP_IN_INIT)
                ;

        /* Wait for 1848 to autocal */
        ADWRITE(sc, AD1848_IADDR, SP_TEST_AND_INIT);
        while(ADREAD(sc, AD1848_IDATA) & AUTO_CAL_IN_PROG)
                ;

        return 1;
bad:
        return 0;
}

/* Unmap the I/O ports */
void
ad1848_unmap(struct ad1848_softc *sc)
{
        bus_space_unmap(sc->sc_iot, sc->sc_ioh, AD1848_NPORT);
}

/*
 * Attach hardware to driver, attach hardware driver to audio
 * pseudo-device driver .
 */
void
ad1848_attach(struct ad1848_softc *sc)
{
        int i;
        struct ad1848_volume vol_mid = {220, 220};
        struct ad1848_volume vol_0   = {0, 0};
        struct audio_params pparams, rparams;
        int timeout;

        sc->sc_playrun = 0;
        sc->sc_recrun = 0;

        if (sc->sc_drq != -1) {
                if (isa_dmamap_create(sc->sc_isa, sc->sc_drq, MAX_ISADMA,
                    BUS_DMA_NOWAIT|BUS_DMA_ALLOCNOW)) {
                        printf("ad1848_attach: can't create map for drq %d\n",
                            sc->sc_drq);
                        return;
                }
        }
        if (sc->sc_recdrq != -1 && sc->sc_recdrq != sc->sc_drq) {
                if (isa_dmamap_create(sc->sc_isa, sc->sc_recdrq, MAX_ISADMA,
                    BUS_DMA_NOWAIT|BUS_DMA_ALLOCNOW)) {
                        printf("ad1848_attach: can't create map for second drq %d\n",
                            sc->sc_recdrq);
                        return;
                }
        }

        /* Initialize the ad1848... */
        for (i = 0; i < 0x10; i++) {
                ad_write(sc, i, ad1848_init_values[i]);
                timeout = AD1848_TIMO;
                while (timeout > 0 && ADREAD(sc, AD1848_IADDR) & SP_IN_INIT)
                        timeout--;
        }
        /* need 2 separate drqs for mode 2 */
        if ((sc->mode == 2) &&
            ((sc->sc_recdrq == -1) || (sc->sc_recdrq == sc->sc_drq))) {
                ad_write(sc, SP_MISC_INFO, ad_read(sc, SP_MISC_INFO) & ~MODE2);
                if (!(ad_read(sc, SP_MISC_INFO) & MODE2))
                        sc->mode = 1;
        }
        /* ...and additional CS4231 stuff too */
        if (sc->mode == 2) {
                ad_write(sc, SP_INTERFACE_CONFIG, 0); /* disable SINGLE_DMA */
                for (i = 0x10; i < 0x20; i++) {
                        if (ad1848_init_values[i] != 0) {
                                ad_write(sc, i, ad1848_init_values[i]);
                                timeout = AD1848_TIMO;
                                while (timeout > 0 && 
                                    ADREAD(sc, AD1848_IADDR) & SP_IN_INIT)
                                        timeout--;
                        }
                }
        }
        ad1848_reset(sc);

        pparams = ad1848_audio_default;
        rparams = ad1848_audio_default;
        (void) ad1848_set_params(sc, AUMODE_RECORD|AUMODE_PLAY, 0,
            &pparams, &rparams);

        /* Set default gains */
        (void) ad1848_set_rec_gain(sc, &vol_mid);
        (void) ad1848_set_channel_gain(sc, AD1848_DAC_CHANNEL, &vol_mid);
        (void) ad1848_set_channel_gain(sc, AD1848_MONITOR_CHANNEL, &vol_0);
        /* CD volume */
        (void) ad1848_set_channel_gain(sc, AD1848_AUX1_CHANNEL, &vol_mid);
        if (sc->mode == 2) {
                 /* CD volume */
                (void) ad1848_set_channel_gain(sc, AD1848_AUX2_CHANNEL, &vol_mid);
                (void) ad1848_set_channel_gain(sc, AD1848_LINE_CHANNEL, &vol_mid);
                (void) ad1848_set_channel_gain(sc, AD1848_MONO_CHANNEL, &vol_0);
                sc->mute[AD1848_MONO_CHANNEL] = MUTE_ALL;
        } else
                (void) ad1848_set_channel_gain(sc, AD1848_AUX2_CHANNEL, &vol_0);

        /* Set default port */
        (void) ad1848_set_rec_port(sc, MIC_IN_PORT);

        if (sc->chip_name)
                printf(": %s", sc->chip_name);
}

/*
 * Various routines to interface to higher level audio driver
 */
struct ad1848_mixerinfo {
        int left_reg;
        int right_reg;
        int atten_bits;
        int atten_mask;
} mixer_channel_info[] = {
        { SP_LEFT_AUX2_CONTROL, SP_RIGHT_AUX2_CONTROL, AUX_INPUT_ATTEN_BITS,
                AUX_INPUT_ATTEN_MASK },
        { SP_LEFT_AUX1_CONTROL, SP_RIGHT_AUX1_CONTROL, AUX_INPUT_ATTEN_BITS,
                AUX_INPUT_ATTEN_MASK },
        { SP_LEFT_OUTPUT_CONTROL, SP_RIGHT_OUTPUT_CONTROL, OUTPUT_ATTEN_BITS,
                OUTPUT_ATTEN_MASK }, 
        { CS_LEFT_LINE_CONTROL, CS_RIGHT_LINE_CONTROL, LINE_INPUT_ATTEN_BITS,
                LINE_INPUT_ATTEN_MASK },
        { CS_MONO_IO_CONTROL, 0, MONO_INPUT_ATTEN_BITS, MONO_INPUT_ATTEN_MASK },
        { SP_DIGITAL_MIX, 0, OUTPUT_ATTEN_BITS, MIX_ATTEN_MASK }
};

/*
 *  This function doesn't set the mute flags but does use them.
 *  The mute flags reflect the mutes that have been applied by the user.
 *  However, the driver occasionally wants to mute devices (e.g. when changing
 *  sampling rate). These operations should not affect the mute flags.
 */
void 
ad1848_mute_channel(struct ad1848_softc *sc, int device, int mute)
{
        u_char reg;

        reg = ad_read(sc, mixer_channel_info[device].left_reg);

        if (mute & MUTE_LEFT) {
                if (device == AD1848_MONITOR_CHANNEL) {
                        ad_write(sc, mixer_channel_info[device].left_reg,
                            reg & 0xFE);
                } else {
                        ad_write(sc, mixer_channel_info[device].left_reg,
                            reg | 0x80);
                }
        } else if (!(sc->mute[device] & MUTE_LEFT)) {
                if (device == AD1848_MONITOR_CHANNEL) {
                        ad_write(sc, mixer_channel_info[device].left_reg,
                            reg | 0x01);
                } else {
                        ad_write(sc, mixer_channel_info[device].left_reg,
                            reg & ~0x80);
                }
        }

        if (!mixer_channel_info[device].right_reg) {
                return;
        }

        reg = ad_read(sc, mixer_channel_info[device].right_reg);

        if (mute & MUTE_RIGHT) {
                ad_write(sc, mixer_channel_info[device].right_reg, reg | 0x80);
        } else if (!(sc->mute[device] & MUTE_RIGHT)) {
                ad_write(sc, mixer_channel_info[device].right_reg, reg & ~0x80);
        }
}

int
ad1848_set_channel_gain(struct ad1848_softc *sc, int device,
    struct ad1848_volume *gp)
{
        struct ad1848_mixerinfo *info = &mixer_channel_info[device];
        u_char reg;
        u_int atten;

        sc->gains[device] = *gp;

        atten = ((AUDIO_MAX_GAIN - gp->left) * info->atten_bits) /
            AUDIO_MAX_GAIN;

        reg = ad_read(sc, info->left_reg) & (info->atten_mask);
        if (device == AD1848_MONITOR_CHANNEL)
                reg |= ((atten & info->atten_bits) << 2);
        else
                reg |= ((atten & info->atten_bits));

        ad_write(sc, info->left_reg, reg);

        if (!info->right_reg)
                return 0;

        atten = ((AUDIO_MAX_GAIN - gp->right) * info->atten_bits) /
            AUDIO_MAX_GAIN;
        reg = ad_read(sc, info->right_reg);
        reg &= (info->atten_mask);
        ad_write(sc, info->right_reg, (atten & info->atten_bits) | reg);

        return 0;
}

int
ad1848_get_device_gain(struct ad1848_softc *sc, int device,
    struct ad1848_volume *gp)
{
        *gp = sc->gains[device];
        return 0;
}

int
ad1848_get_rec_gain(struct ad1848_softc *sc, struct ad1848_volume *gp)
{
        *gp = sc->rec_gain;
        return 0;
}

int
ad1848_set_rec_gain(struct ad1848_softc *sc, struct ad1848_volume *gp)
{
        u_char reg, gain;
        
        DPRINTF(("ad1848_set_rec_gain: %d:%d\n", gp->left, gp->right));

        sc->rec_gain = *gp;

        gain = (gp->left * GAIN_22_5) / AUDIO_MAX_GAIN;
        reg = ad_read(sc, SP_LEFT_INPUT_CONTROL);
        reg &= INPUT_GAIN_MASK;
        ad_write(sc, SP_LEFT_INPUT_CONTROL, (gain & 0x0f) | reg);

        gain = (gp->right * GAIN_22_5) / AUDIO_MAX_GAIN;
        reg = ad_read(sc, SP_RIGHT_INPUT_CONTROL);
        reg &= INPUT_GAIN_MASK;
        ad_write(sc, SP_RIGHT_INPUT_CONTROL, (gain & 0x0f) | reg);

        return 0;
}

void
ad1848_mute_monitor(void *addr, int mute)
{
        struct ad1848_softc *sc = addr;

        DPRINTF(("ad1848_mute_monitor: %smuting\n", mute ? "" : "un"));
        if (sc->mode == 2) {
                ad1848_mute_channel(sc, AD1848_DAC_CHANNEL,
                    mute ? MUTE_ALL : 0);
                ad1848_mute_channel(sc, AD1848_MONO_CHANNEL,
                    mute ? MUTE_MONO : 0);
                ad1848_mute_channel(sc, AD1848_LINE_CHANNEL,
                    mute ? MUTE_ALL : 0);
        }

        ad1848_mute_channel(sc, AD1848_AUX2_CHANNEL, mute ? MUTE_ALL : 0);
        ad1848_mute_channel(sc, AD1848_AUX1_CHANNEL, mute ? MUTE_ALL : 0);
}

int
ad1848_set_mic_gain(struct ad1848_softc *sc, struct ad1848_volume *gp)
{
        u_char reg;

        DPRINTF(("cs4231_set_mic_gain: %d\n", gp->left));

        if (gp->left > AUDIO_MAX_GAIN / 2) {
                sc->mic_gain_on = 1;
                reg = ad_read(sc, SP_LEFT_INPUT_CONTROL);
                ad_write(sc, SP_LEFT_INPUT_CONTROL,
                    reg | INPUT_MIC_GAIN_ENABLE);
        } else {
                sc->mic_gain_on = 0;
                reg = ad_read(sc, SP_LEFT_INPUT_CONTROL);
                ad_write(sc, SP_LEFT_INPUT_CONTROL,
                    reg & ~INPUT_MIC_GAIN_ENABLE);
        }

        return 0;
}

int
ad1848_get_mic_gain(struct ad1848_softc *sc, struct ad1848_volume *gp)
{
        if (sc->mic_gain_on)
                gp->left = gp->right = AUDIO_MAX_GAIN;
        else
                gp->left = gp->right = AUDIO_MIN_GAIN;

        return 0;
}


static ad1848_devmap_t *ad1848_mixer_find_dev(ad1848_devmap_t *, int, mixer_ctrl_t *);

static ad1848_devmap_t *
ad1848_mixer_find_dev(ad1848_devmap_t *map, int cnt, mixer_ctrl_t *cp)
{
        int idx;

        for (idx = 0; idx < cnt; idx++) {
                if (map[idx].id == cp->dev) {
                        return &map[idx];
                }
        }
        return NULL;
}

int
ad1848_mixer_get_port(struct ad1848_softc *ac, struct ad1848_devmap *map,
    int cnt, mixer_ctrl_t *cp)
{
        ad1848_devmap_t *entry;
        struct ad1848_volume vol;
        int error = EINVAL;
        int dev;

        if (!(entry = ad1848_mixer_find_dev(map, cnt, cp)))
                return (ENXIO);

        dev = entry->dev;
        mtx_enter(&audio_lock);
        switch (entry->kind) {
        case AD1848_KIND_LVL:
                if (cp->type != AUDIO_MIXER_VALUE)
                        break;
                if (dev < AD1848_AUX2_CHANNEL ||
                    dev > AD1848_MONITOR_CHANNEL)
                        break;
                if (cp->un.value.num_channels != 1 &&
                    mixer_channel_info[dev].right_reg == 0) 
                        break;
                error = ad1848_get_device_gain(ac, dev, &vol);
                if (!error)
                        ad1848_from_vol(cp, &vol);
                break;

        case AD1848_KIND_MUTE:
                if (cp->type != AUDIO_MIXER_ENUM)
                        break;
                cp->un.ord = ac->mute[dev] ? 1 : 0;
                error = 0;
                break;

        case AD1848_KIND_RECORDGAIN:
                if (cp->type != AUDIO_MIXER_VALUE)
                        break;
                error = ad1848_get_rec_gain(ac, &vol);
                if (!error)
                        ad1848_from_vol(cp, &vol);
                break;

        case AD1848_KIND_MICGAIN:
                if (cp->type != AUDIO_MIXER_VALUE)
                        break;
                error = ad1848_get_mic_gain(ac, &vol);
                if (!error)
                        ad1848_from_vol(cp, &vol);
                break;

        case AD1848_KIND_RECORDSOURCE:
                if (cp->type != AUDIO_MIXER_ENUM)
                        break;
                cp->un.ord = ad1848_get_rec_port(ac);
                error = 0;
                break;

        default:
                printf("Invalid kind\n");
                break;
        }
        mtx_leave(&audio_lock);
        return error;
}

int      
ad1848_mixer_set_port(struct ad1848_softc *ac, struct ad1848_devmap *map,
    int cnt, mixer_ctrl_t *cp)
{
        ad1848_devmap_t *entry;
        struct ad1848_volume vol;
        int error = EINVAL;
        int dev;

        if (!(entry = ad1848_mixer_find_dev(map, cnt, cp)))
                return (ENXIO);

        dev = entry->dev;
        mtx_enter(&audio_lock);
        switch (entry->kind) {
        case AD1848_KIND_LVL:
                if (cp->type != AUDIO_MIXER_VALUE)
                        break;
                if (dev < AD1848_AUX2_CHANNEL ||
                    dev > AD1848_MONITOR_CHANNEL)
                        break;
                if (cp->un.value.num_channels != 1 &&
                    mixer_channel_info[dev].right_reg == 0) 
                        break;
                ad1848_to_vol(cp, &vol);
                error = ad1848_set_channel_gain(ac, dev, &vol);
                break;

        case AD1848_KIND_MUTE:
                if (cp->type != AUDIO_MIXER_ENUM)
                        break;
                ac->mute[dev] = (cp->un.ord ? MUTE_ALL : 0);
                ad1848_mute_channel(ac, dev, ac->mute[dev]);
                error = 0;
                break;

        case AD1848_KIND_RECORDGAIN:
                if (cp->type != AUDIO_MIXER_VALUE)
                        break;
                ad1848_to_vol(cp, &vol);
                error = ad1848_set_rec_gain(ac, &vol);
                break;

        case AD1848_KIND_MICGAIN:
                if (cp->type != AUDIO_MIXER_VALUE)
                        break;
                ad1848_to_vol(cp, &vol);
                error = ad1848_set_mic_gain(ac, &vol);
                break;

        case AD1848_KIND_RECORDSOURCE:
                if (cp->type != AUDIO_MIXER_ENUM)
                        break;
                error = ad1848_set_rec_port(ac,  cp->un.ord);
                break;

        default:
                printf("Invalid kind\n");
                break;
        }
        mtx_leave(&audio_lock);
        return (error);
}

int
ad1848_set_params(void *addr, int setmode, int usemode, struct audio_params *p,
    struct audio_params *r)
{
        struct ad1848_softc *sc = addr;
        int error, bits, enc;

        DPRINTF(("ad1848_set_params: %d %d %d %ld\n", 
             p->encoding, p->precision, p->channels, p->sample_rate));

        enc = p->encoding;
        switch (enc) {
        case AUDIO_ENCODING_SLINEAR_LE:
                if (p->precision == 8)
                        return EINVAL;
                break;
        case AUDIO_ENCODING_SLINEAR_BE:
                if (p->precision == 16)
                        return EINVAL;
                break;
        case AUDIO_ENCODING_ULINEAR_LE:
                if (p->precision == 16)
                        return EINVAL;
                break;
        case AUDIO_ENCODING_ULINEAR_BE:
                if (p->precision == 16)
                        return EINVAL;
                break;
        }
        switch (enc) {
        case AUDIO_ENCODING_ULAW:
                p->precision = 8;
                bits = FMT_ULAW;
                break;
        case AUDIO_ENCODING_ALAW:
                p->precision = 8;
                bits = FMT_ALAW;
                break;
        case AUDIO_ENCODING_SLINEAR_LE:
                if (p->precision == 16)
                        bits = FMT_TWOS_COMP;
                else
                        return EINVAL;
                break;
        case AUDIO_ENCODING_SLINEAR_BE:
                if (p->precision == 16)
                        bits = FMT_TWOS_COMP_BE;
                else
                        return EINVAL;
                break;
        case AUDIO_ENCODING_ULINEAR_LE:
                if (p->precision == 8)
                        bits = FMT_PCM8;
                else
                        return EINVAL;
                break;
        default:
                return EINVAL;
        }

        if (p->channels < 1 || p->channels > 2)
                return EINVAL;

        error = ad1848_set_speed(sc, &p->sample_rate);
        if (error)
                return error;

        p->bps = AUDIO_BPS(p->precision);
        r->bps = AUDIO_BPS(r->precision);
        p->msb = 1;
        r->msb = 1;

        sc->format_bits = bits;
        sc->channels = p->channels;
        sc->precision = p->precision;
        sc->need_commit = 1;

        DPRINTF(("ad1848_set_params succeeded, bits=%x\n", bits));
        return (0);
}

int
ad1848_set_rec_port(struct ad1848_softc *sc, int port)
{
        u_char inp, reg;

        DPRINTF(("ad1848_set_rec_port: 0x%x\n", port));

        if (port == MIC_IN_PORT) {
                inp = MIC_INPUT;
        } else if (port == LINE_IN_PORT) {
                inp = LINE_INPUT;
        } else if (port == DAC_IN_PORT) {
                inp = MIXED_DAC_INPUT;
        } else if (sc->mode == 2 && port == AUX1_IN_PORT) {
                inp = AUX_INPUT;
        } else
                return EINVAL;

        reg = ad_read(sc, SP_LEFT_INPUT_CONTROL);
        reg &= INPUT_SOURCE_MASK;
        ad_write(sc, SP_LEFT_INPUT_CONTROL, (inp | reg));

        reg = ad_read(sc, SP_RIGHT_INPUT_CONTROL);
        reg &= INPUT_SOURCE_MASK;
        ad_write(sc, SP_RIGHT_INPUT_CONTROL, (inp | reg));

        sc->rec_port = port;

        return 0;
}

int
ad1848_get_rec_port(struct ad1848_softc *sc)
{
        return sc->rec_port;
}

int
ad1848_round_blocksize(void *addr, int blk)
{
        /* Round to a multiple of the biggest sample size. */
        blk = (blk + 3) & -4;

        return blk;
}

int
ad1848_open(void *addr, int flags)
{
        struct ad1848_softc *sc = addr;

        DPRINTF(("ad1848_open: sc=%p\n", sc));

        if ((flags & (FWRITE | FREAD)) == (FWRITE | FREAD) && sc->mode != 2)
                return ENXIO;

        sc->sc_pintr = sc->sc_parg = NULL;
        sc->sc_rintr = sc->sc_rarg = NULL;

        /* Enable interrupts */
        DPRINTF(("ad1848_open: enable intrs\n"));
        ad_write(sc, SP_PIN_CONTROL,
            INTERRUPT_ENABLE | ad_read(sc, SP_PIN_CONTROL));

#ifdef AUDIO_DEBUG
        if (ad1848debug > 2)
                ad1848_dump_regs(sc);
#endif

        return 0;
}

/*
 * Close function is called at splaudio().
 */
void
ad1848_close(void *addr)
{
        struct ad1848_softc *sc = addr;
        u_char r;

        ad1848_halt_output(sc);
        ad1848_halt_input(sc);

        sc->sc_pintr = NULL;
        sc->sc_rintr = NULL;

        DPRINTF(("ad1848_close: stop DMA\n"));

        ad_write(sc, SP_LOWER_BASE_COUNT, (u_char)0);
        ad_write(sc, SP_UPPER_BASE_COUNT, (u_char)0);

        /* Disable interrupts */
        DPRINTF(("ad1848_close: disable intrs\n"));
        ad_write(sc, SP_PIN_CONTROL, 
            ad_read(sc, SP_PIN_CONTROL) & ~INTERRUPT_ENABLE);

        DPRINTF(("ad1848_close: disable capture and playback\n"));
        r = ad_read(sc, SP_INTERFACE_CONFIG);
        r &= ~(CAPTURE_ENABLE | PLAYBACK_ENABLE);
        ad_write(sc, SP_INTERFACE_CONFIG, r);

#ifdef AUDIO_DEBUG
        if (ad1848debug > 2)
                ad1848_dump_regs(sc);
#endif
}

/*
 * Lower-level routines
 */
int
ad1848_commit_settings(void *addr)
{
        struct ad1848_softc *sc = addr;
        int timeout;
        u_char fs;

        if (!sc->need_commit)
                return 0;

        mtx_enter(&audio_lock);

        ad1848_mute_monitor(sc, 1);

        /* Enables changes to the format select reg */
        ad_set_MCE(sc, 1);

        fs = sc->speed_bits | sc->format_bits;

        if (sc->channels == 2)
                fs |= FMT_STEREO;

        ad_write(sc, SP_CLOCK_DATA_FORMAT, fs);

        /*
         * If mode == 2 (CS4231), set I28 also. It's the capture format
         * register.
         */
        if (sc->mode == 2) {
                /* Gravis Ultrasound MAX SDK sources says something about
                 * errata sheets, with the implication that these inb()s
                 * are necessary.
                 */
                (void)ADREAD(sc, AD1848_IDATA);
                (void)ADREAD(sc, AD1848_IDATA);

                /*
                 * Write to I8 starts resynchronization. Wait until it
                 * completes.
                 */
                timeout = AD1848_TIMO;
                while (timeout > 0 && ADREAD(sc, AD1848_IADDR) == SP_IN_INIT)
                        timeout--;

                ad_write(sc, CS_REC_FORMAT, fs);
                /* Gravis Ultrasound MAX SDK sources says something about
                 * errata sheets, with the implication that these inb()s
                 * are necessary.
                 */
                (void)ADREAD(sc, AD1848_IDATA);
                (void)ADREAD(sc, AD1848_IDATA);
                /* Now wait for resync for capture side of the house */
        }
        /*
         * Write to I8 starts resynchronization. Wait until it completes.
         */
        timeout = AD1848_TIMO;
        while (timeout > 0 && ADREAD(sc, AD1848_IADDR) == SP_IN_INIT)
                timeout--;

        if (ADREAD(sc, AD1848_IADDR) == SP_IN_INIT)
                printf("ad1848_commit: Auto calibration timed out\n");

        /*
         * Starts the calibration process and enters playback mode after it.
         */
        ad_set_MCE(sc, 0);
        wait_for_calibration(sc);

        ad1848_mute_monitor(sc, 0);

        mtx_leave(&audio_lock);
        
        sc->need_commit = 0;

        return 0;
}

void
ad1848_reset(struct ad1848_softc *sc)
{
        u_char r;

        DPRINTF(("ad1848_reset\n"));

        /* Clear the PEN and CEN bits */
        r = ad_read(sc, SP_INTERFACE_CONFIG);
        r &= ~(CAPTURE_ENABLE | PLAYBACK_ENABLE);
        ad_write(sc, SP_INTERFACE_CONFIG, r);

        /* Clear interrupt status */
        if (sc->mode == 2)
                ad_write(sc, CS_IRQ_STATUS, 0);
        ADWRITE(sc, AD1848_STATUS, 0);

#ifdef AUDIO_DEBUG
        if (ad1848debug > 2)
                ad1848_dump_regs(sc);
#endif
}

int
ad1848_set_speed(struct ad1848_softc *sc, u_long *argp)
{
        /*
         * The sampling speed is encoded in the least significant nible of I8.
         * The LSB selects the clock source (0=24.576 MHz, 1=16.9344 MHz) and
         * other three bits select the divisor (indirectly):
         *
         * The available speeds are in the following table. Keep the speeds in
         * the increasing order.
         */
        typedef struct {
                int     speed;
                u_char  bits;
        } speed_struct;
        u_long arg = *argp;

        static speed_struct speed_table[] =  {
                {5510, (0 << 1) | 1},
                {5510, (0 << 1) | 1},
                {6620, (7 << 1) | 1},
                {8000, (0 << 1) | 0},
                {9600, (7 << 1) | 0},
                {11025, (1 << 1) | 1},
                {16000, (1 << 1) | 0},
                {18900, (2 << 1) | 1},
                {22050, (3 << 1) | 1},
                {27420, (2 << 1) | 0},
                {32000, (3 << 1) | 0},
                {33075, (6 << 1) | 1},
                {37800, (4 << 1) | 1},
                {44100, (5 << 1) | 1},
                {48000, (6 << 1) | 0}
        };

        int i, n, selected = -1;

        n = sizeof(speed_table) / sizeof(speed_struct);

        if (arg < speed_table[0].speed)
                selected = 0;
        if (arg > speed_table[n - 1].speed)
                selected = n - 1;

        for (i = 1 /*really*/ ; selected == -1 && i < n; i++) {
                if (speed_table[i].speed == arg)
                        selected = i;
                else if (speed_table[i].speed > arg) {
                        int diff1, diff2;

                        diff1 = arg - speed_table[i - 1].speed;
                        diff2 = speed_table[i].speed - arg;

                        if (diff1 < diff2)
                                selected = i - 1;
                        else
                                selected = i;
                }
        }

        if (selected == -1) {
                printf("ad1848: Can't find speed???\n");
                selected = 3;
        }

        sc->speed_bits = speed_table[selected].bits;
        sc->need_commit = 1;
        *argp = speed_table[selected].speed;

        return 0;
}

/*
 * Halt a DMA in progress.
 */
int
ad1848_halt_output(void *addr)
{
        struct ad1848_softc *sc = addr;
        u_char reg;

        DPRINTF(("ad1848: ad1848_halt_output\n"));
        mtx_enter(&audio_lock);
        reg = ad_read(sc, SP_INTERFACE_CONFIG);
        ad_write(sc, SP_INTERFACE_CONFIG, (reg & ~PLAYBACK_ENABLE));

        if (sc->sc_playrun == 1) {
                isa_dmaabort(sc->sc_isa, sc->sc_drq);
                sc->sc_playrun = 0;
        }
        mtx_leave(&audio_lock);
        return 0;
}

int
ad1848_halt_input(void *addr)
{
        struct ad1848_softc *sc = addr;
        u_char reg;

        DPRINTF(("ad1848: ad1848_halt_input\n"));
        mtx_enter(&audio_lock);
        reg = ad_read(sc, SP_INTERFACE_CONFIG);
        ad_write(sc, SP_INTERFACE_CONFIG, (reg & ~CAPTURE_ENABLE));

        if (sc->sc_recrun == 1) {
                isa_dmaabort(sc->sc_isa, sc->sc_recdrq);
                sc->sc_recrun = 0;
        }
        mtx_leave(&audio_lock);
        return 0;
}

int
ad1848_trigger_input(void *addr, void *start, void *end, int blksize,
    void (*intr)(void *), void *arg, struct audio_params *param)
{
        struct ad1848_softc *sc = addr;
        u_char reg;

        if (sc->sc_recdrq == -1) {
                DPRINTF(("ad1848_trigger_input: invalid recording drq\n"));
                return ENXIO;
        }
        mtx_enter(&audio_lock);
        isa_dmastart(sc->sc_isa, sc->sc_recdrq, start,
            (char *)end - (char *)start, NULL, DMAMODE_READ | DMAMODE_LOOP,
            BUS_DMA_NOWAIT);

        sc->sc_recrun = 1;
        sc->sc_rintr = intr;
        sc->sc_rarg = arg;

        blksize = (blksize * NBBY) / (param->precision * param->channels) - 1;

        if (sc->mode == 2) {
                ad_write(sc, CS_LOWER_REC_CNT, (blksize & 0xff));
                ad_write(sc, CS_UPPER_REC_CNT, ((blksize >> 8) & 0xff));
        } else {
                ad_write(sc, SP_LOWER_BASE_COUNT, blksize & 0xff);
                ad_write(sc, SP_UPPER_BASE_COUNT, (blksize >> 8) & 0xff);
        }

        reg = ad_read(sc, SP_INTERFACE_CONFIG);
        ad_write(sc, SP_INTERFACE_CONFIG, (CAPTURE_ENABLE | reg));

#ifdef AUDIO_DEBUG
        if (ad1848debug > 1)
                printf("ad1848_trigger_input: started capture\n");
#endif
        mtx_leave(&audio_lock);
        return 0;
}

int
ad1848_trigger_output(void *addr, void *start, void *end, int blksize,
    void (*intr)(void *), void *arg, struct audio_params *param)
{
        struct ad1848_softc *sc = addr;
        u_char reg;

        mtx_enter(&audio_lock);
        isa_dmastart(sc->sc_isa, sc->sc_drq, start,
            (char *)end - (char *)start, NULL,
            DMAMODE_WRITE | DMAMODE_LOOP, BUS_DMA_NOWAIT);

        sc->sc_playrun = 1;
        sc->sc_pintr = intr;
        sc->sc_parg = arg;

        blksize = (blksize * NBBY) / (param->precision * param->channels) - 1;

        ad_write(sc, SP_LOWER_BASE_COUNT, blksize & 0xff);
        ad_write(sc, SP_UPPER_BASE_COUNT, (blksize >> 8) & 0xff);

        reg = ad_read(sc, SP_INTERFACE_CONFIG);
        ad_write(sc, SP_INTERFACE_CONFIG, (PLAYBACK_ENABLE | reg));

#ifdef AUDIO_DEBUG
        if (ad1848debug > 1)
                printf("ad1848_trigger_output: started playback\n");
#endif
        mtx_leave(&audio_lock);
        return 0;
}

int
ad1848_intr(void *arg)
{
        struct ad1848_softc *sc = arg;
        int retval = 0;
        u_char status;

        mtx_enter(&audio_lock);
        /* Get intr status */
        status = ADREAD(sc, AD1848_STATUS);
        
#ifdef AUDIO_DEBUG
        if (ad1848debug > 1)
                printf("ad1848_intr: mode=%d pintr=%p prun=%d rintr=%p rrun=%d status=0x%x\n",
                    sc->mode, sc->sc_pintr, sc->sc_playrun, sc->sc_rintr, sc->sc_recrun, status);
#endif

        /* Handle interrupt */
        if ((status & INTERRUPT_STATUS) != 0) {
                if (sc->mode == 2) {
                        status = ad_read(sc, CS_IRQ_STATUS);
#ifdef AUDIO_DEBUG
                        if (ad1848debug > 2)
                                printf("ad1848_intr: cs_irq_status=0x%x (play=0x%x rec0x%x)\n",
                                    status, CS_IRQ_PI, CS_IRQ_CI);
#endif
                        if ((status & CS_IRQ_PI) && sc->sc_playrun) {
                                (*sc->sc_pintr)(sc->sc_parg);
                                retval = 1;
                        }
                        if ((status & CS_IRQ_CI) && sc->sc_recrun) {
                                (*sc->sc_rintr)(sc->sc_rarg);
                                retval = 1;
                        }
                } else {
                        if (sc->sc_playrun) {
                                (*sc->sc_pintr)(sc->sc_parg);
                                retval = 1;
                        } else if (sc->sc_recrun) {
                                (*sc->sc_rintr)(sc->sc_rarg);
                                retval = 1;
                        }
                }
                /* clear interrupt */
                ADWRITE(sc, AD1848_STATUS, 0);
        }
        mtx_leave(&audio_lock);
        return(retval);
}

void *
ad1848_malloc(void *addr, int direction, size_t size, int pool, int flags)
{
        struct ad1848_softc *sc = addr;
        int drq;

        if (direction == AUMODE_PLAY)
                drq = sc->sc_drq;
        else
                drq = sc->sc_recdrq;

        return isa_malloc(sc->sc_isa, drq, size, pool, flags);
}

void
ad1848_free(void *addr, void *ptr, int pool)
{
        isa_free(ptr, pool);
}

size_t
ad1848_round(void *addr, int direction, size_t size)
{
        if (size > MAX_ISADMA)
                size = MAX_ISADMA;
        return size;
}