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