root/sys/dev/pci/emuxki.c
/*      $NetBSD: emuxki.c,v 1.79 2024/06/08 21:02:29 andvar Exp $       */

/*-
 * Copyright (c) 2001, 2007 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Yannick Montulet, and by Andrew Doran.
 *
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
 */

/*
 * EMU10K1 single voice driver
 * o. only 1 voice playback, 1 recording
 * o. only s16le 2ch 48k
 * This makes it simple to control buffers and interrupts
 * while satisfying playback and recording quality.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: emuxki.c,v 1.79 2024/06/08 21:02:29 andvar Exp $");

#include <sys/param.h>
#include <sys/device.h>
#include <sys/module.h>
#include <sys/errno.h>
#include <sys/systm.h>
#include <sys/audioio.h>
#include <sys/mutex.h>
#include <sys/kmem.h>
#include <sys/fcntl.h>

#include <sys/bus.h>
#include <sys/intr.h>

#include <dev/pci/emuxkireg.h>
#include <dev/pci/emuxkivar.h>
#include <dev/pci/emuxki_boards.h>

/* #define EMUXKI_DEBUG 1 */
#ifdef EMUXKI_DEBUG
#define emudebug EMUXKI_DEBUG
# define DPRINTF(fmt...)        do { if (emudebug) printf(fmt); } while (0)
# define DPRINTFN(n,fmt...)     do { if (emudebug>=(n)) printf(fmt); } while (0)
#else
# define DPRINTF(fmt...)        __nothing
# define DPRINTFN(n,fmt...)     __nothing
#endif

/*
 * PCI
 * Note: emuxki's page table entry uses only 31bit addressing.
 *       (Maybe, later chip has 32bit mode, but it isn't used now.)
 */

#define EMU_PCI_CBIO            (0x10)

/* blackmagic */
#define X1(x)           ((sc->sc_type & EMUXKI_AUDIGY) ? EMU_A_##x : EMU_##x)
#define X2(x, y)        ((sc->sc_type & EMUXKI_AUDIGY) \
    ? EMU_A_##x(EMU_A_##y) : EMU_##x(EMU_##y))
#define EMU_A_DSP_FX            EMU_DSP_FX
#define EMU_A_DSP_IN_AC97       EMU_DSP_IN_AC97

/* prototypes */
static struct dmamem *dmamem_alloc(struct emuxki_softc *, size_t);
static void     dmamem_free(struct dmamem *);
static void     dmamem_sync(struct dmamem *, int);
static uint8_t  emuxki_readio_1(struct emuxki_softc *, int) __unused;
static uint16_t emuxki_readio_2(struct emuxki_softc *, int);
static uint32_t emuxki_readio_4(struct emuxki_softc *, int);
static void     emuxki_writeio_1(struct emuxki_softc *, int, uint8_t);
static void     emuxki_writeio_2(struct emuxki_softc *, int, uint16_t);
static void     emuxki_writeio_4(struct emuxki_softc *, int, uint32_t);
static uint32_t emuxki_readptr(struct emuxki_softc *, int, int, int);
static void     emuxki_writeptr(struct emuxki_softc *, int, int, int, uint32_t);
static uint32_t emuxki_read(struct emuxki_softc *, int, int);
static void     emuxki_write(struct emuxki_softc *, int, int, uint32_t);
static int      emuxki_match(device_t, cfdata_t, void *);
static void     emuxki_attach(device_t, device_t, void *);
static int      emuxki_detach(device_t, int);
static int      emuxki_init(struct emuxki_softc *);
static void     emuxki_dsp_addop(struct emuxki_softc *, uint16_t *, uint8_t,
                    uint16_t, uint16_t, uint16_t, uint16_t);
static void     emuxki_initfx(struct emuxki_softc *);
static void     emuxki_play_start(struct emuxki_softc *, int, uint32_t,
                    uint32_t);
static void     emuxki_play_stop(struct emuxki_softc *, int);

static int      emuxki_query_format(void *, audio_format_query_t *);
static int      emuxki_set_format(void *, int,
                    const audio_params_t *, const audio_params_t *,
                    audio_filter_reg_t *, audio_filter_reg_t *);
static int      emuxki_halt_output(void *);
static int      emuxki_halt_input(void *);
static int      emuxki_intr(void *);
static int      emuxki_getdev(void *, struct audio_device *);
static int      emuxki_set_port(void *, mixer_ctrl_t *);
static int      emuxki_get_port(void *, mixer_ctrl_t *);
static int      emuxki_query_devinfo(void *, mixer_devinfo_t *);
static void     *emuxki_allocm(void *, int, size_t);
static void     emuxki_freem(void *, void *, size_t);
static int      emuxki_round_blocksize(void *, int, int,
                    const audio_params_t *);
static size_t   emuxki_round_buffersize(void *, int, size_t);
static int      emuxki_get_props(void *);
static int      emuxki_trigger_output(void *, void *, void *, int,
                    void (*)(void *), void *, const audio_params_t *);
static int      emuxki_trigger_input(void *, void *, void *, int,
                    void (*)(void *), void *, const audio_params_t *);
static void     emuxki_get_locks(void *, kmutex_t **, kmutex_t **);

static int      emuxki_ac97_init(struct emuxki_softc *);
static int      emuxki_ac97_attach(void *, struct ac97_codec_if *);
static int      emuxki_ac97_read(void *, uint8_t, uint16_t *);
static int      emuxki_ac97_write(void *, uint8_t, uint16_t);
static int      emuxki_ac97_reset(void *);
static enum ac97_host_flags     emuxki_ac97_flags(void *);


CFATTACH_DECL_NEW(emuxki, sizeof(struct emuxki_softc),
    emuxki_match, emuxki_attach, emuxki_detach, NULL);

static const struct audio_hw_if emuxki_hw_if = {
        .query_format           = emuxki_query_format,
        .set_format             = emuxki_set_format,
        .round_blocksize        = emuxki_round_blocksize,
        .halt_output            = emuxki_halt_output,
        .halt_input             = emuxki_halt_input,
        .getdev                 = emuxki_getdev,
        .set_port               = emuxki_set_port,
        .get_port               = emuxki_get_port,
        .query_devinfo          = emuxki_query_devinfo,
        .allocm                 = emuxki_allocm,
        .freem                  = emuxki_freem,
        .round_buffersize       = emuxki_round_buffersize,
        .get_props              = emuxki_get_props,
        .trigger_output         = emuxki_trigger_output,
        .trigger_input          = emuxki_trigger_input,
        .get_locks              = emuxki_get_locks,
};

static const struct audio_format emuxki_formats[] = {
        {
                .mode           = AUMODE_PLAY | AUMODE_RECORD,
                .encoding       = AUDIO_ENCODING_SLINEAR_LE,
                .validbits      = 16,
                .precision      = 16,
                .channels       = 2,
                .channel_mask   = AUFMT_STEREO,
                .frequency_type = 1,
                .frequency      = { 48000 },
        }
};
#define EMUXKI_NFORMATS __arraycount(emuxki_formats)

/*
 * dma memory
 */

static struct dmamem *
dmamem_alloc(struct emuxki_softc *sc, size_t size)
{
        struct dmamem *mem;

        KASSERT(!mutex_owned(&sc->sc_intr_lock));

        /* Allocate memory for structure */
        mem = kmem_alloc(sizeof(*mem), KM_SLEEP);
        mem->dmat = sc->sc_dmat;
        mem->size = size;
        mem->align = EMU_DMA_ALIGN;
        mem->nsegs = EMU_DMA_NSEGS;
        mem->bound = 0;

        mem->segs = kmem_alloc(mem->nsegs * sizeof(*(mem->segs)), KM_SLEEP);

        if (bus_dmamem_alloc(mem->dmat, mem->size, mem->align, mem->bound,
            mem->segs, mem->nsegs, &mem->rsegs, BUS_DMA_WAITOK)) {
                device_printf(sc->sc_dev,
                    "%s bus_dmamem_alloc failed\n", __func__);
                goto memfree;
        }

        if (bus_dmamem_map(mem->dmat, mem->segs, mem->nsegs, mem->size,
            &mem->kaddr, BUS_DMA_WAITOK | BUS_DMA_COHERENT)) {
                device_printf(sc->sc_dev,
                    "%s bus_dmamem_map failed\n", __func__);
                goto free;
        }

        if (bus_dmamap_create(mem->dmat, mem->size, mem->nsegs, mem->size,
            mem->bound, BUS_DMA_WAITOK, &mem->map)) {
                device_printf(sc->sc_dev,
                    "%s bus_dmamap_create failed\n", __func__);
                goto unmap;
        }

        if (bus_dmamap_load(mem->dmat, mem->map, mem->kaddr,
            mem->size, NULL, BUS_DMA_WAITOK)) {
                device_printf(sc->sc_dev,
                    "%s bus_dmamap_load failed\n", __func__);
                goto destroy;
        }

        return mem;

destroy:
        bus_dmamap_destroy(mem->dmat, mem->map);
unmap:
        bus_dmamem_unmap(mem->dmat, mem->kaddr, mem->size);
free:
        bus_dmamem_free(mem->dmat, mem->segs, mem->nsegs);
memfree:
        kmem_free(mem->segs, mem->nsegs * sizeof(*(mem->segs)));
        kmem_free(mem, sizeof(*mem));

        return NULL;
}

static void
dmamem_free(struct dmamem *mem)
{

        bus_dmamap_unload(mem->dmat, mem->map);
        bus_dmamap_destroy(mem->dmat, mem->map);
        bus_dmamem_unmap(mem->dmat, mem->kaddr, mem->size);
        bus_dmamem_free(mem->dmat, mem->segs, mem->nsegs);

        kmem_free(mem->segs, mem->nsegs * sizeof(*(mem->segs)));
        kmem_free(mem, sizeof(*mem));
}

static void
dmamem_sync(struct dmamem *mem, int ops)
{

        bus_dmamap_sync(mem->dmat, mem->map, 0, mem->size, ops);
}


/*
 * I/O register access
 */

static uint8_t
emuxki_readio_1(struct emuxki_softc *sc, int addr)
{

        return bus_space_read_1(sc->sc_iot, sc->sc_ioh, addr);
}

static void
emuxki_writeio_1(struct emuxki_softc *sc, int addr, uint8_t data)
{

        bus_space_write_1(sc->sc_iot, sc->sc_ioh, addr, data);
}

static uint16_t
emuxki_readio_2(struct emuxki_softc *sc, int addr)
{

        return bus_space_read_2(sc->sc_iot, sc->sc_ioh, addr);
}

static void
emuxki_writeio_2(struct emuxki_softc *sc, int addr, uint16_t data)
{

        bus_space_write_2(sc->sc_iot, sc->sc_ioh, addr, data);
}

static uint32_t
emuxki_readio_4(struct emuxki_softc *sc, int addr)
{

        return bus_space_read_4(sc->sc_iot, sc->sc_ioh, addr);
}

static void
emuxki_writeio_4(struct emuxki_softc *sc, int addr, uint32_t data)
{

        bus_space_write_4(sc->sc_iot, sc->sc_ioh, addr, data);
}

static uint32_t
emuxki_readptr(struct emuxki_softc *sc, int aptr, int dptr, int addr)
{
        uint32_t data;

        mutex_spin_enter(&sc->sc_index_lock);
        emuxki_writeio_4(sc, aptr, addr);
        data = emuxki_readio_4(sc, dptr);
        mutex_spin_exit(&sc->sc_index_lock);
        return data;
}

static void
emuxki_writeptr(struct emuxki_softc *sc, int aptr, int dptr, int addr,
    uint32_t data)
{

        mutex_spin_enter(&sc->sc_index_lock);
        emuxki_writeio_4(sc, aptr, addr);
        emuxki_writeio_4(sc, dptr, data);
        mutex_spin_exit(&sc->sc_index_lock);
}

static uint32_t
emuxki_read(struct emuxki_softc *sc, int ch, int addr)
{

        /* Original HENTAI addressing is never supported. */
        KASSERT((addr & 0xff000000) == 0);

        return emuxki_readptr(sc, EMU_PTR, EMU_DATA, (addr << 16) + ch);
}

static void
emuxki_write(struct emuxki_softc *sc, int ch, int addr, uint32_t data)
{

        /* Original HENTAI addressing is never supported. */
        KASSERT((addr & 0xff000000) == 0);

        emuxki_writeptr(sc, EMU_PTR, EMU_DATA, (addr << 16) + ch, data);
}

/*
 * MD driver
 */

static int
emuxki_match(device_t parent, cfdata_t match, void *aux)
{
        struct pci_attach_args *pa;
        pcireg_t reg;

        pa = aux;

        reg = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_SUBSYS_ID_REG);
        if (emuxki_board_lookup(PCI_VENDOR(pa->pa_id),
                                PCI_PRODUCT(pa->pa_id), reg,
                                PCI_REVISION(pa->pa_class)) != NULL)
                return 1;

        return 0;
}

static void
emuxki_attach(device_t parent, device_t self, void *aux)
{
        struct emuxki_softc *sc;
        struct pci_attach_args *pa;
        const struct emuxki_board *sb;
        pci_intr_handle_t ih;
        const char *intrstr;
        char intrbuf[PCI_INTRSTR_LEN];
        pcireg_t reg;

        sc = device_private(self);
        sc->sc_dev = self;
        pa = aux;

        reg = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_SUBSYS_ID_REG);
        sb = emuxki_board_lookup(PCI_VENDOR(pa->pa_id),
                                      PCI_PRODUCT(pa->pa_id), reg,
                                      PCI_REVISION(pa->pa_class));
        KASSERT(sb != NULL);

        pci_aprint_devinfo(pa, "Audio controller");
        aprint_normal_dev(self, "%s [%s]\n", sb->sb_name, sb->sb_board);
        DPRINTF("dmat=%p\n", (char *)pa->pa_dmat);

        mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
        mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_AUDIO);
        mutex_init(&sc->sc_index_lock, MUTEX_DEFAULT, IPL_AUDIO);

        sc->sc_pc   = pa->pa_pc;

        /* EMU10K1 can only address 31 bits (2GB) */
        if (bus_dmatag_subregion(pa->pa_dmat, 0, ((uint32_t)1 << 31) - 1,
            &(sc->sc_dmat), BUS_DMA_NOWAIT) != 0) {
                aprint_error_dev(self,
                    "WARNING: failed to restrict dma range,"
                    " falling back to parent bus dma range\n");
                sc->sc_dmat = pa->pa_dmat;
        }

        reg = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_COMMAND_STATUS_REG);
        reg |= PCI_COMMAND_IO_ENABLE | PCI_COMMAND_MASTER_ENABLE |
            PCI_COMMAND_MEM_ENABLE;
        pci_conf_write(pa->pa_pc, pa->pa_tag, PCI_COMMAND_STATUS_REG, reg);

        if (pci_mapreg_map(pa, EMU_PCI_CBIO, PCI_MAPREG_TYPE_IO, 0,
            &sc->sc_iot, &sc->sc_ioh, &sc->sc_iob, &sc->sc_ios)) {
                aprint_error(": can't map iospace\n");
                return;
        }

        if (pci_intr_map(pa, &ih)) {
                aprint_error_dev(self, "couldn't map interrupt\n");
                goto unmap;
        }

        intrstr = pci_intr_string(pa->pa_pc, ih, intrbuf, sizeof(intrbuf));
        sc->sc_ih = pci_intr_establish_xname(pa->pa_pc, ih, IPL_AUDIO,
            emuxki_intr, sc, device_xname(self));
        if (sc->sc_ih == NULL) {
                aprint_error_dev(self, "couldn't establish interrupt");
                if (intrstr != NULL)
                        aprint_error(" at %s", intrstr);
                aprint_error("\n");
                goto unmap;
        }
        aprint_normal_dev(self, "interrupting at %s\n", intrstr);

        /* XXX it's unknown whether APS is made from Audigy as well */
        sc->sc_type = sb->sb_flags;
        if (sc->sc_type & EMUXKI_AUDIGY2_CA0108) {
                strlcpy(sc->sc_audv.name, "Audigy2+CA0108",
                    sizeof(sc->sc_audv.name));
        } else if (sc->sc_type & EMUXKI_AUDIGY2) {
                strlcpy(sc->sc_audv.name, "Audigy2", sizeof(sc->sc_audv.name));
        } else if (sc->sc_type & EMUXKI_AUDIGY) {
                strlcpy(sc->sc_audv.name, "Audigy", sizeof(sc->sc_audv.name));
        } else if (sc->sc_type & EMUXKI_APS) {
                strlcpy(sc->sc_audv.name, "E-mu APS", sizeof(sc->sc_audv.name));
        } else {
                strlcpy(sc->sc_audv.name, "SB Live!", sizeof(sc->sc_audv.name));
        }
        snprintf(sc->sc_audv.version, sizeof(sc->sc_audv.version), "0x%02x",
            PCI_REVISION(pa->pa_class));
        strlcpy(sc->sc_audv.config, "emuxki", sizeof(sc->sc_audv.config));

        if (emuxki_init(sc)) {
                aprint_error("emuxki_init error\n");
                goto intrdis;
        }
        if (emuxki_ac97_init(sc)) {
                aprint_error("emuxki_ac97_init error\n");
                goto intrdis;
        }

        sc->sc_audev = audio_attach_mi(&emuxki_hw_if, sc, self);
        if (sc->sc_audev == NULL) {
                aprint_error("audio_attach_mi error\n");
                goto intrdis;
        }

        return;

intrdis:
        pci_intr_disestablish(sc->sc_pc, sc->sc_ih);
unmap:
        bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_ios);
        return;
}

static int
emuxki_detach(device_t self, int flags)
{
        struct emuxki_softc *sc = device_private(self);
        int error;

        error = config_detach_children(self, flags);
        if (error)
                return error;

        /* All voices should be stopped now but add some code here if not */
        emuxki_writeio_4(sc, EMU_HCFG,
            EMU_HCFG_LOCKSOUNDCACHE |
            EMU_HCFG_LOCKTANKCACHE_MASK |
            EMU_HCFG_MUTEBUTTONENABLE);
        emuxki_writeio_4(sc, EMU_INTE, 0);

        /* Disable any Channels interrupts */
        emuxki_write(sc, 0, EMU_CLIEL, 0);
        emuxki_write(sc, 0, EMU_CLIEH, 0);
        emuxki_write(sc, 0, EMU_SOLEL, 0);
        emuxki_write(sc, 0, EMU_SOLEH, 0);

        /* stop DSP */
        emuxki_write(sc, 0, X1(DBG), X1(DBG_SINGLE_STEP));

        dmamem_free(sc->ptb);

        pci_intr_disestablish(sc->sc_pc, sc->sc_ih);
        bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_ios);

        mutex_destroy(&sc->sc_lock);
        mutex_destroy(&sc->sc_intr_lock);
        mutex_destroy(&sc->sc_index_lock);

        return 0;
}

static int
emuxki_init(struct emuxki_softc *sc)
{
        int i;
        uint32_t spcs;
        uint32_t hcfg;

        /* clear AUDIO bit */
        emuxki_writeio_4(sc, EMU_HCFG,
            EMU_HCFG_LOCKSOUNDCACHE |
            EMU_HCFG_LOCKTANKCACHE_MASK |
            EMU_HCFG_MUTEBUTTONENABLE);

        /* mask interrupt without PCIERR */
        emuxki_writeio_4(sc, EMU_INTE,
            EMU_INTE_SAMPLERATER | /* always on this bit */
            EMU_INTE_PCIERRENABLE);

        /* disable all channel interrupt */
        emuxki_write(sc, 0, EMU_CLIEL, 0);
        emuxki_write(sc, 0, EMU_CLIEH, 0);
        emuxki_write(sc, 0, EMU_SOLEL, 0);
        emuxki_write(sc, 0, EMU_SOLEH, 0);

        /* Set recording buffers sizes to zero */
        emuxki_write(sc, 0, EMU_MICBS, EMU_RECBS_BUFSIZE_NONE);
        emuxki_write(sc, 0, EMU_MICBA, 0);
        emuxki_write(sc, 0, EMU_FXBS, EMU_RECBS_BUFSIZE_NONE);
        emuxki_write(sc, 0, EMU_FXBA, 0);
        emuxki_write(sc, 0, EMU_ADCBS, EMU_RECBS_BUFSIZE_NONE);
        emuxki_write(sc, 0, EMU_ADCBA, 0);

        if(sc->sc_type & EMUXKI_AUDIGY) {
                emuxki_write(sc, 0, EMU_SPBYPASS, EMU_SPBYPASS_24_BITS);
                emuxki_write(sc, 0, EMU_AC97SLOT,
                    EMU_AC97SLOT_CENTER | EMU_AC97SLOT_LFE);
        }

        /* Initialize all channels to stopped and no effects */
        for (i = 0; i < EMU_NUMCHAN; i++) {
                emuxki_write(sc, i, EMU_CHAN_DCYSUSV, 0x7f7f);
                emuxki_write(sc, i, EMU_CHAN_IP, EMU_CHAN_IP_UNITY);
                emuxki_write(sc, i, EMU_CHAN_VTFT, 0xffff);
                emuxki_write(sc, i, EMU_CHAN_CVCF, 0xffff);
                emuxki_write(sc, i, EMU_CHAN_PTRX, 0);
                emuxki_write(sc, i, EMU_CHAN_CPF, 0);
                emuxki_write(sc, i, EMU_CHAN_CCR, 0);
                emuxki_write(sc, i, EMU_CHAN_PSST, 0);
                emuxki_write(sc, i, EMU_CHAN_DSL, 0);
                emuxki_write(sc, i, EMU_CHAN_CCCA, EMU_CHAN_CCCA_INTERPROM_1);
                emuxki_write(sc, i, EMU_CHAN_Z1, 0);
                emuxki_write(sc, i, EMU_CHAN_Z2, 0);
                emuxki_write(sc, i, EMU_CHAN_MAPA, 0xffffffff);
                emuxki_write(sc, i, EMU_CHAN_MAPB, 0xffffffff);
                emuxki_write(sc, i, EMU_CHAN_FXRT, 0x32100000);
                emuxki_write(sc, i, EMU_CHAN_ATKHLDM, 0);
                emuxki_write(sc, i, EMU_CHAN_DCYSUSM, 0);
                emuxki_write(sc, i, EMU_CHAN_IFATN, 0xffff);
                emuxki_write(sc, i, EMU_CHAN_PEFE, 0x007f);
                emuxki_write(sc, i, EMU_CHAN_FMMOD, 0);
                emuxki_write(sc, i, EMU_CHAN_TREMFRQ, 0);
                emuxki_write(sc, i, EMU_CHAN_FM2FRQ2, 0);
                emuxki_write(sc, i, EMU_CHAN_TEMPENV, 0);

                /* these are last so OFF prevents writing */
                emuxki_write(sc, i, EMU_CHAN_LFOVAL2, 0x8000);
                emuxki_write(sc, i, EMU_CHAN_LFOVAL1, 0x8000);
                emuxki_write(sc, i, EMU_CHAN_ATKHLDV, 0x7f7f);
                emuxki_write(sc, i, EMU_CHAN_ENVVOL, 0);
                emuxki_write(sc, i, EMU_CHAN_ENVVAL, 0x8000);
        }

        /* set digital outputs format */
        spcs = EMU_SPCS_CLKACCY_1000PPM |
               EMU_SPCS_SAMPLERATE_48 |
               EMU_SPCS_CHANNELNUM_LEFT |
               EMU_SPCS_SOURCENUM_UNSPEC |
               EMU_SPCS_GENERATIONSTATUS |
               0x00001200 /* Cat code. */ |
               0x00000000 /* IEC-958 Mode */ |
               EMU_SPCS_EMPHASIS_NONE |
               EMU_SPCS_COPYRIGHT;
        emuxki_write(sc, 0, EMU_SPCS0, spcs);
        emuxki_write(sc, 0, EMU_SPCS1, spcs);
        emuxki_write(sc, 0, EMU_SPCS2, spcs);

        if (sc->sc_type & EMUXKI_AUDIGY2_CA0108) {
                /* Setup SRCMulti_I2S SamplingRate */
                emuxki_write(sc, 0, EMU_A2_SPDIF_SAMPLERATE,
                    emuxki_read(sc, 0, EMU_A2_SPDIF_SAMPLERATE) & 0xfffff1ff);

                /* Setup SRCSel (Enable SPDIF, I2S SRCMulti) */
                emuxki_writeptr(sc, EMU_A2_PTR, EMU_A2_DATA, EMU_A2_SRCSEL,
                    EMU_A2_SRCSEL_ENABLE_SPDIF | EMU_A2_SRCSEL_ENABLE_SRCMULTI);

                /* Setup SRCMulti Input Audio Enable */
                emuxki_writeptr(sc, EMU_A2_PTR, EMU_A2_DATA,
                    0x7b0000, 0xff000000);

                /* Setup SPDIF Out Audio Enable
                 * The Audigy 2 Value has a separate SPDIF out,
                 * so no need for a mixer switch */
                emuxki_writeptr(sc, EMU_A2_PTR, EMU_A2_DATA,
                    0x7a0000, 0xff000000);
                emuxki_writeio_4(sc, EMU_A_IOCFG,
                    emuxki_readio_4(sc, EMU_A_IOCFG) & ~0x8); /* clear bit 3 */
        } else if (sc->sc_type & EMUXKI_AUDIGY2) {
                emuxki_write(sc, 0, EMU_A2_SPDIF_SAMPLERATE,
                    EMU_A2_SPDIF_UNKNOWN);

                emuxki_writeptr(sc, EMU_A2_PTR, EMU_A2_DATA, EMU_A2_SRCSEL,
                    EMU_A2_SRCSEL_ENABLE_SPDIF | EMU_A2_SRCSEL_ENABLE_SRCMULTI);

                emuxki_writeptr(sc, EMU_A2_PTR, EMU_A2_DATA, EMU_A2_SRCMULTI,
                    EMU_A2_SRCMULTI_ENABLE_INPUT);
        }

        /* page table */
        sc->ptb = dmamem_alloc(sc, EMU_MAXPTE * sizeof(uint32_t));
        if (sc->ptb == NULL) {
                device_printf(sc->sc_dev, "ptb allocation error\n");
                return ENOMEM;
        }
        emuxki_write(sc, 0, EMU_PTB, DMAADDR(sc->ptb));

        emuxki_write(sc, 0, EMU_TCBS, 0);       /* This means 16K TCB */
        emuxki_write(sc, 0, EMU_TCB, 0);        /* No TCB use for now */

        /* Let's play with sound processor */
        emuxki_initfx(sc);

        /* enable interrupt */
        emuxki_writeio_4(sc, EMU_INTE,
            emuxki_readio_4(sc, EMU_INTE) |
            EMU_INTE_VOLINCRENABLE |
            EMU_INTE_VOLDECRENABLE |
            EMU_INTE_MUTEENABLE);

        if (sc->sc_type & EMUXKI_AUDIGY2_CA0108) {
                emuxki_writeio_4(sc, EMU_A_IOCFG,
                    0x0060 | emuxki_readio_4(sc, EMU_A_IOCFG));
        } else if (sc->sc_type & EMUXKI_AUDIGY2) {
                emuxki_writeio_4(sc, EMU_A_IOCFG,
                    EMU_A_IOCFG_GPOUT0 | emuxki_readio_4(sc, EMU_A_IOCFG));
        }

        /* enable AUDIO bit */
        hcfg = EMU_HCFG_AUDIOENABLE | EMU_HCFG_AUTOMUTE;

        if (sc->sc_type & EMUXKI_AUDIGY2) {
                hcfg |= EMU_HCFG_AC3ENABLE_CDSPDIF |
                        EMU_HCFG_AC3ENABLE_GPSPDIF;
        } else if (sc->sc_type & EMUXKI_AUDIGY) {
        } else {
                hcfg |= EMU_HCFG_LOCKTANKCACHE_MASK;
        }
        /* joystick not supported now */
        emuxki_writeio_4(sc, EMU_HCFG, hcfg);

        return 0;
}

/*
 * dsp programming
 */

static void
emuxki_dsp_addop(struct emuxki_softc *sc, uint16_t *pc, uint8_t op,
    uint16_t r, uint16_t a, uint16_t x, uint16_t y)
{
        uint32_t loword;
        uint32_t hiword;
        int reg;

        if (sc->sc_type & EMUXKI_AUDIGY) {
                reg = EMU_A_MICROCODEBASE;
                loword = (x << 12) & EMU_A_DSP_LOWORD_OPX_MASK;
                loword |= y & EMU_A_DSP_LOWORD_OPY_MASK;
                hiword = (op << 24) & EMU_A_DSP_HIWORD_OPCODE_MASK;
                hiword |= (r << 12) & EMU_A_DSP_HIWORD_RESULT_MASK;
                hiword |= a & EMU_A_DSP_HIWORD_OPA_MASK;
        } else {
                reg = EMU_MICROCODEBASE;
                loword = (x << 10) & EMU_DSP_LOWORD_OPX_MASK;
                loword |= y & EMU_DSP_LOWORD_OPY_MASK;
                hiword = (op << 20) & EMU_DSP_HIWORD_OPCODE_MASK;
                hiword |= (r << 10) & EMU_DSP_HIWORD_RESULT_MASK;
                hiword |= a & EMU_DSP_HIWORD_OPA_MASK;
        }

        reg += (*pc) * 2;
        /* must ordering; lo, hi */
        emuxki_write(sc, 0, reg, loword);
        emuxki_write(sc, 0, reg + 1, hiword);

        (*pc)++;
}

static void
emuxki_initfx(struct emuxki_softc *sc)
{
        uint16_t pc;

        /* Set all GPRs to 0 */
        for (pc = 0; pc < 256; pc++)
                emuxki_write(sc, 0, EMU_DSP_GPR(pc), 0);
        for (pc = 0; pc < 160; pc++) {
                emuxki_write(sc, 0, EMU_TANKMEMDATAREGBASE + pc, 0);
                emuxki_write(sc, 0, EMU_TANKMEMADDRREGBASE + pc, 0);
        }

        /* stop DSP, single step mode */
        emuxki_write(sc, 0, X1(DBG), X1(DBG_SINGLE_STEP));

        /* XXX: delay (48kHz equiv. 21us) if needed */

        /* start DSP programming */
        pc = 0;

        /* OUT[L/R] = 0 + FX[L/R] * 1 */
        emuxki_dsp_addop(sc, &pc, EMU_DSP_OP_MACINTS,
            X2(DSP_OUTL, DSP_OUT_A_FRONT),
            X1(DSP_CST(0)),
            X1(DSP_FX(0)),
            X1(DSP_CST(1)));
        emuxki_dsp_addop(sc, &pc, EMU_DSP_OP_MACINTS,
            X2(DSP_OUTR, DSP_OUT_A_FRONT),
            X1(DSP_CST(0)),
            X1(DSP_FX(1)),
            X1(DSP_CST(1)));
#if 0
        /* XXX: rear feature??? */
        /* Rear OUT[L/R] = 0 + FX[L/R] * 1 */
        emuxki_dsp_addop(sc, &pc, EMU_DSP_OP_MACINTS,
            X2(DSP_OUTL, DSP_OUT_A_REAR),
            X1(DSP_CST(0)),
            X1(DSP_FX(0)),
            X1(DSP_CST(1)));
        emuxki_dsp_addop(sc, &pc, EMU_DSP_OP_MACINTS,
            X2(DSP_OUTR, DSP_OUT_A_REAR),
            X1(DSP_CST(0)),
            X1(DSP_FX(1)),
            X1(DSP_CST(1)));
#endif
        /* ADC recording[L/R] = AC97 In[L/R] */
        emuxki_dsp_addop(sc, &pc, EMU_DSP_OP_ACC3,
            X2(DSP_OUTL, DSP_OUT_ADC),
            X2(DSP_INL, DSP_IN_AC97),
            X1(DSP_CST(0)),
            X1(DSP_CST(0)));
        emuxki_dsp_addop(sc, &pc, EMU_DSP_OP_ACC3,
            X2(DSP_OUTR, DSP_OUT_ADC),
            X2(DSP_INR, DSP_IN_AC97),
            X1(DSP_CST(0)),
            X1(DSP_CST(0)));

        /* fill NOP the rest of the microcode */
        while (pc < 512) {
                emuxki_dsp_addop(sc, &pc, EMU_DSP_OP_ACC3,
                    X1(DSP_CST(0)),
                    X1(DSP_CST(0)),
                    X1(DSP_CST(0)),
                    X1(DSP_CST(0)));
        }

        /* clear single step flag, run DSP */
        emuxki_write(sc, 0, X1(DBG), 0);
}

/*
 * operations
 */

static void
emuxki_play_start(struct emuxki_softc *sc, int ch, uint32_t start, uint32_t end)
{
        uint32_t pitch;
        uint32_t volume;

        /* 48kHz:16384 = 128/375 */
        pitch = sc->play.sample_rate * 128 / 375;
        volume = 32767;

        emuxki_write(sc, ch, EMU_CHAN_DSL,
            (0 << 24) | /* send amount D = 0 */
            end);

        emuxki_write(sc, ch, EMU_CHAN_PSST,
            (0 << 24) | /* send amount C = 0 */
            start);

        emuxki_write(sc, ch, EMU_CHAN_VTFT,
            (volume << 16) |
            (0xffff));  /* cutoff filter = none */

        emuxki_write(sc, ch, EMU_CHAN_CVCF,
            (volume << 16) |
            (0xffff));  /* cutoff filter = none */

        emuxki_write(sc, ch, EMU_CHAN_PTRX,
            (pitch << 16) |
            ((ch == 0 ? 0x7f : 0) << 8) |       /* send amount A = 255,0(L) */
            ((ch == 0 ? 0 : 0x7f)));            /* send amount B = 0,255(R) */

        /* set the pitch to start */
        emuxki_write(sc, ch, EMU_CHAN_CPF,
            (pitch << 16) |
            EMU_CHAN_CPF_STEREO_MASK);  /* stereo only */
}

static void
emuxki_play_stop(struct emuxki_softc *sc, int ch)
{

        /* pitch = 0 to stop playing */
        emuxki_write(sc, ch, EMU_CHAN_CPF, EMU_CHAN_CPF_STOP_MASK);
        /* volume = 0 */
        emuxki_write(sc, ch, EMU_CHAN_CVCF, 0);
}

static void
emuxki_timer_start(struct emuxki_softc *sc)
{
        uint32_t timer;

        /* frame count of half PTE at 16bit, 2ch, 48kHz */
        timer = EMU_PTESIZE / 4 / 2;

        /* EMU_TIMER is 16bit register */
        emuxki_writeio_2(sc, EMU_TIMER, timer);
        emuxki_writeio_4(sc, EMU_INTE,
            emuxki_readio_4(sc, EMU_INTE) |
                EMU_INTE_INTERTIMERENB);
        DPRINTF("timer start\n");
}

static void
emuxki_timer_stop(struct emuxki_softc *sc)
{

        emuxki_writeio_4(sc, EMU_INTE,
            emuxki_readio_4(sc, EMU_INTE) &
                ~EMU_INTE_INTERTIMERENB);
        /* EMU_TIMER is 16bit register */
        emuxki_writeio_2(sc, EMU_TIMER, 0);
        DPRINTF("timer stop\n");
}

/*
 * audio interface
 */

static int
emuxki_query_format(void *hdl, audio_format_query_t *afp)
{

        return audio_query_format(emuxki_formats, EMUXKI_NFORMATS, afp);
}

static int
emuxki_set_format(void *hdl, int setmode,
    const audio_params_t *play, const audio_params_t *rec,
    audio_filter_reg_t *pfil, audio_filter_reg_t *rfil)
{
        struct emuxki_softc *sc = hdl;

        if ((setmode & AUMODE_PLAY))
                sc->play = *play;
        if ((setmode & AUMODE_RECORD))
                sc->rec = *rec;
        return 0;
}

static int
emuxki_halt_output(void *hdl)
{
        struct emuxki_softc *sc = hdl;

        emuxki_timer_stop(sc);
        emuxki_play_stop(sc, 0);
        emuxki_play_stop(sc, 1);
        return 0;
}

static int
emuxki_halt_input(void *hdl)
{
        struct emuxki_softc *sc = hdl;

        /* stop ADC */
        emuxki_write(sc, 0, EMU_ADCCR, 0);

        /* disable interrupt */
        emuxki_writeio_4(sc, EMU_INTE,
            emuxki_readio_4(sc, EMU_INTE) &
                ~EMU_INTE_ADCBUFENABLE);

        return 0;
}

static int
emuxki_intr(void *hdl)
{
        struct emuxki_softc *sc = hdl;
        uint32_t ipr;
        uint32_t curaddr;
        int handled = 0;

        mutex_spin_enter(&sc->sc_intr_lock);

        ipr = emuxki_readio_4(sc, EMU_IPR);
        DPRINTFN(3, "emuxki: ipr=%08x\n", ipr);
        if (sc->pintr && (ipr & EMU_IPR_INTERVALTIMER)) {
                /* read ch 0 */
                curaddr = emuxki_read(sc, 0, EMU_CHAN_CCCA) &
                    EMU_CHAN_CCCA_CURRADDR_MASK;
                DPRINTFN(3, "curaddr=%08x\n", curaddr);
                curaddr *= sc->pframesize;

                if (curaddr < sc->poffset)
                        curaddr += sc->plength;
                if (curaddr >= sc->poffset + sc->pblksize) {
                        dmamem_sync(sc->pmem, BUS_DMASYNC_POSTWRITE);
                        sc->pintr(sc->pintrarg);
                        sc->poffset += sc->pblksize;
                        if (sc->poffset >= sc->plength) {
                                sc->poffset -= sc->plength;
                        }
                        dmamem_sync(sc->pmem, BUS_DMASYNC_PREWRITE);
                }
                handled = 1;
        }

        if (sc->rintr &&
            (ipr & (EMU_IPR_ADCBUFHALFFULL | EMU_IPR_ADCBUFFULL))) {
                char *src;
                char *dst;

                /* Record DMA buffer has just 2 blocks */
                src = KERNADDR(sc->rmem);
                if (ipr & EMU_IPR_ADCBUFFULL) {
                        /* 2nd block */
                        src += EMU_REC_DMABLKSIZE;
                }
                dst = (char *)sc->rptr + sc->rcurrent;

                dmamem_sync(sc->rmem, BUS_DMASYNC_POSTREAD);
                memcpy(dst, src, EMU_REC_DMABLKSIZE);
                /* for next trans */
                dmamem_sync(sc->rmem, BUS_DMASYNC_PREREAD);
                sc->rcurrent += EMU_REC_DMABLKSIZE;

                if (sc->rcurrent >= sc->roffset + sc->rblksize) {
                        sc->rintr(sc->rintrarg);
                        sc->roffset += sc->rblksize;
                        if (sc->roffset >= sc->rlength) {
                                sc->roffset = 0;
                                sc->rcurrent = 0;
                        }
                }

                handled = 1;
        }

#if defined(EMUXKI_DEBUG)
        if (!handled) {
                char buf[1024];
                snprintb(buf, sizeof(buf),
                    "\20"
                    "\x19""RATETRCHANGE"
                    "\x18""FXDSP"
                    "\x17""FORCEINT"
                    "\x16""PCIERROR"
                    "\x15""VOLINCR"
                    "\x14""VOLDECR"
                    "\x13""MUTE"
                    "\x12""MICBUFFULL"
                    "\x11""MICBUFHALFFULL"
                    "\x10""ADCBUFFULL"
                    "\x0f""ADCBUFHALFFULL"
                    "\x0e""EFXBUFFULL"
                    "\x0d""EFXBUFHALFFULL"
                    "\x0c""GPSPDIFSTCHANGE"
                    "\x0b""CDROMSTCHANGE"
                    /*     INTERVALTIMER */
                    "\x09""MIDITRANSBUFE"
                    "\x08""MIDIRECVBUFE"
                    "\x07""CHANNELLOOP"
                    , ipr);
                DPRINTF("unexpected intr: %s\n", buf);

                /* for debugging (must not handle if !DEBUG) */
                handled = 1;
        }
#endif

        /* Reset interrupt bit */
        emuxki_writeio_4(sc, EMU_IPR, ipr);

        mutex_spin_exit(&sc->sc_intr_lock);

        /* Interrupt handler must return !=0 if handled */
        return handled;
}

static int
emuxki_getdev(void *hdl, struct audio_device *dev)
{
        struct emuxki_softc *sc = hdl;

        *dev = sc->sc_audv;
        return 0;
}

static int
emuxki_set_port(void *hdl, mixer_ctrl_t *mctl)
{
        struct emuxki_softc *sc = hdl;

        return sc->codecif->vtbl->mixer_set_port(sc->codecif, mctl);
}

static int
emuxki_get_port(void *hdl, mixer_ctrl_t *mctl)
{
        struct emuxki_softc *sc = hdl;

        return sc->codecif->vtbl->mixer_get_port(sc->codecif, mctl);
}

static int
emuxki_query_devinfo(void *hdl, mixer_devinfo_t *minfo)
{
        struct emuxki_softc *sc = hdl;

        return sc->codecif->vtbl->query_devinfo(sc->codecif, minfo);
}

static void *
emuxki_allocm(void *hdl, int direction, size_t size)
{
        struct emuxki_softc *sc = hdl;

        if (direction == AUMODE_PLAY) {
                if (sc->pmem) {
                        panic("pmem already allocated\n");
                        return NULL;
                }
                sc->pmem = dmamem_alloc(sc, size);
                return KERNADDR(sc->pmem);
        } else {
                /* rmem is fixed size internal DMA buffer */
                if (sc->rmem) {
                        panic("rmem already allocated\n");
                        return NULL;
                }
                /* rmem fixed size */
                sc->rmem = dmamem_alloc(sc, EMU_REC_DMASIZE);

                /* recording MI buffer is normal kmem, software trans. */
                sc->rptr = kmem_alloc(size, KM_SLEEP);
                return sc->rptr;
        }
}

static void
emuxki_freem(void *hdl, void *ptr, size_t size)
{
        struct emuxki_softc *sc = hdl;

        if (sc->pmem && ptr == KERNADDR(sc->pmem)) {
                dmamem_free(sc->pmem);
                sc->pmem = NULL;
        }
        if (sc->rmem && ptr == sc->rptr) {
                dmamem_free(sc->rmem);
                sc->rmem = NULL;
                kmem_free(sc->rptr, size);
                sc->rptr = NULL;
        }
}

/*
 * blocksize rounding to EMU_PTESIZE. It is for easy to drive.
 */
static int
emuxki_round_blocksize(void *hdl, int blksize,
    int mode, const audio_params_t* param)
{

        /*
         * This is not necessary for recording, but symmetric for easy.
         * For recording buffer/block size requirements of hardware,
         * see EMU_RECBS_BUFSIZE_*
         */
        if (blksize < EMU_PTESIZE)
                blksize = EMU_PTESIZE;
        return rounddown(blksize, EMU_PTESIZE);
}

static size_t
emuxki_round_buffersize(void *hdl, int direction, size_t bsize)
{

        /* This is not necessary for recording, but symmetric for easy */
        if (bsize < EMU_MINPTE * EMU_PTESIZE) {
                bsize = EMU_MINPTE * EMU_PTESIZE;
        } else if (bsize > EMU_MAXPTE * EMU_PTESIZE) {
                bsize = EMU_MAXPTE * EMU_PTESIZE;
        }
        return roundup(bsize, EMU_PTESIZE);
}

static int
emuxki_get_props(void *hdl)
{

        return AUDIO_PROP_PLAYBACK | AUDIO_PROP_CAPTURE |
            AUDIO_PROP_INDEPENDENT | AUDIO_PROP_FULLDUPLEX;
}

static int
emuxki_trigger_output(void *hdl, void *start, void *end, int blksize,
    void (*intr)(void *), void *arg, const audio_params_t *params)
{
        struct emuxki_softc *sc = hdl;
        int npage;
        uint32_t *kptb;
        bus_addr_t dpmem;
        int i;
        uint32_t hwstart;
        uint32_t hwend;

        if (sc->pmem == NULL)
                panic("pmem == NULL\n");
        if (start != KERNADDR(sc->pmem))
                panic("start != KERNADDR(sc->pmem)\n");

        sc->pframesize = 4;     /* channels * bit / 8 = 2*16/8=4 */
        sc->pblksize = blksize;
        sc->plength = (char *)end - (char *)start;
        sc->poffset = 0;
        npage = roundup(sc->plength, EMU_PTESIZE);

        kptb = KERNADDR(sc->ptb);
        dpmem = DMAADDR(sc->pmem);
        for (i = 0; i < npage; i++) {
                kptb[i] = htole32(dpmem << 1);
                dpmem += EMU_PTESIZE;
        }
        dmamem_sync(sc->ptb, BUS_DMASYNC_PREWRITE);

        hwstart = 0;
        hwend = hwstart + sc->plength / sc->pframesize;

        sc->pintr = intr;
        sc->pintrarg = arg;

        dmamem_sync(sc->pmem, BUS_DMASYNC_PREWRITE);

        emuxki_play_start(sc, 0, hwstart, hwend);
        emuxki_play_start(sc, 1, hwstart, hwend);

        emuxki_timer_start(sc);

        return 0;
}

/*
 * Recording uses temporary buffer.  Because it can use ADC_HALF/FULL
 * interrupts and this method doesn't conflict with playback.
 */

static int
emuxki_trigger_input(void *hdl, void *start, void *end, int blksize,
    void (*intr)(void *), void *arg, const audio_params_t *params)
{
        struct emuxki_softc *sc = hdl;

        if (sc->rmem == NULL)
                panic("rmem == NULL\n");
        if (start != sc->rptr)
                panic("start != sc->rptr\n");

        sc->rframesize = 4;     /* channels * bit / 8 = 2*16/8=4 */
        sc->rblksize = blksize;
        sc->rlength = (char *)end - (char *)start;
        sc->roffset = 0;
        sc->rcurrent = 0;

        sc->rintr = intr;
        sc->rintrarg = arg;

        /*
         * Memo:
         *  recording source is selected by AC97
         *  AC97 input source routes to ADC by FX(DSP)
         *
         * Must keep following sequence order
         */

        /* first, stop ADC */
        emuxki_write(sc, 0, EMU_ADCCR, 0);
        emuxki_write(sc, 0, EMU_ADCBA, 0);
        emuxki_write(sc, 0, EMU_ADCBS, 0);

        dmamem_sync(sc->rmem, BUS_DMASYNC_PREREAD);

        /* ADC interrupt enable */
        emuxki_writeio_4(sc, EMU_INTE,
            emuxki_readio_4(sc, EMU_INTE) |
                EMU_INTE_ADCBUFENABLE);

        /* ADC Enable */
        /* stereo, 48kHz, enable */
        emuxki_write(sc, 0, EMU_ADCCR,
            X1(ADCCR_LCHANENABLE) | X1(ADCCR_RCHANENABLE));

        /* ADC buffer address */
        emuxki_write(sc, 0, X1(ADCIDX), 0);
        emuxki_write(sc, 0, EMU_ADCBA, DMAADDR(sc->rmem));

        /* ADC buffer size, to start */
        emuxki_write(sc, 0, EMU_ADCBS, EMU_REC_BUFSIZE_RECBS);

        return 0;
}

static void
emuxki_get_locks(void *hdl, kmutex_t **intr, kmutex_t **proc)
{
        struct emuxki_softc *sc = hdl;

        *intr = &sc->sc_intr_lock;
        *proc = &sc->sc_lock;
}

/*
 * AC97
 */

static int
emuxki_ac97_init(struct emuxki_softc *sc)
{

        sc->hostif.arg = sc;
        sc->hostif.attach = emuxki_ac97_attach;
        sc->hostif.read = emuxki_ac97_read;
        sc->hostif.write = emuxki_ac97_write;
        sc->hostif.reset = emuxki_ac97_reset;
        sc->hostif.flags = emuxki_ac97_flags;
        return ac97_attach(&sc->hostif, sc->sc_dev, &sc->sc_lock);
}

/*
 * AC97 callbacks
 */

static int
emuxki_ac97_attach(void *hdl, struct ac97_codec_if *codecif)
{
        struct emuxki_softc *sc = hdl;

        sc->codecif = codecif;
        return 0;
}

static int
emuxki_ac97_read(void *hdl, uint8_t reg, uint16_t *val)
{
        struct emuxki_softc *sc = hdl;

        mutex_spin_enter(&sc->sc_index_lock);
        emuxki_writeio_1(sc, EMU_AC97ADDR, reg);
        *val = emuxki_readio_2(sc, EMU_AC97DATA);
        mutex_spin_exit(&sc->sc_index_lock);

        return 0;
}

static int
emuxki_ac97_write(void *hdl, uint8_t reg, uint16_t val)
{
        struct emuxki_softc *sc = hdl;

        mutex_spin_enter(&sc->sc_index_lock);
        emuxki_writeio_1(sc, EMU_AC97ADDR, reg);
        emuxki_writeio_2(sc, EMU_AC97DATA, val);
        mutex_spin_exit(&sc->sc_index_lock);

        return 0;
}

static int
emuxki_ac97_reset(void *hdl)
{

        return 0;
}

static enum ac97_host_flags
emuxki_ac97_flags(void *hdl)
{

        return AC97_HOST_SWAPPED_CHANNELS;
}

MODULE(MODULE_CLASS_DRIVER, emuxki, "pci,audio");

#ifdef _MODULE
#include "ioconf.c"
#endif

static int
emuxki_modcmd(modcmd_t cmd, void *opaque)
{
        int error = 0;

        switch (cmd) {
        case MODULE_CMD_INIT:
#ifdef _MODULE
                error = config_init_component(cfdriver_ioconf_emuxki,
                    cfattach_ioconf_emuxki, cfdata_ioconf_emuxki);
#endif
                return error;
        case MODULE_CMD_FINI:
#ifdef _MODULE
                error = config_fini_component(cfdriver_ioconf_emuxki,
                    cfattach_ioconf_emuxki, cfdata_ioconf_emuxki);
#endif
                return error;
        default:
                return ENOTTY;
        }
}