/*      $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;
       }
}