/*-
 * Copyright (c) 1999 Jason L. Wright (jason@thought.net)
 * Copyright (c) 2004 Pyun YongHyeon
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR 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.
 *
 * Effort sponsored in part by the Defense Advanced Research Projects
 * Agency (DARPA) and Air Force Research Laboratory, Air Force
 * Materiel Command, USAF, under agreement number F30602-01-2-0537.
 *
 *	from: OpenBSD: cs4231.c,v 1.21 2003/07/03 20:36:07 jason Exp
 */

/*
 * Driver for CS4231 based audio found in some sun4m systems (cs4231)
 * based on ideas from the S/Linux project and the NetBSD project.
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD: release/9.0.0/sys/dev/sound/sbus/cs4231.c 215034 2010-11-09 10:59:09Z brucec $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/resource.h>

#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/openfirm.h>
#include <machine/bus.h>
#include <machine/ofw_machdep.h>

#ifdef HAVE_KERNEL_OPTION_HEADERS
#include "opt_snd.h"
#endif

#include <dev/sound/pcm/sound.h>
#include <dev/sound/sbus/apcdmareg.h>
#include <dev/sound/sbus/cs4231.h>

#include <sparc64/sbus/sbusvar.h>
#include <sparc64/ebus/ebusreg.h>

#include "mixer_if.h"

/*
 * The driver supports CS4231A audio chips found on Sbus/Ebus based 
 * UltraSPARCs. Though, CS4231A says it supports full-duplex mode, I
 * doubt it due to the lack of independent sampling frequency register
 * for playback/capture.
 * Since I couldn't find any documentation for APCDMA programming
 * information, I guessed the usage of APCDMA from that of OpenBSD's
 * driver. The EBDMA information of PCIO can be obtained from
 *  http://solutions.sun.com/embedded/databook/web/microprocessors/pcio.html
 * And CS4231A datasheet can also be obtained from
 *  ftp://ftp.alsa-project.org/pub/manuals/cirrus/4231a.pdf
 *
 * Audio capture(recording) was not tested at all and may have bugs.
 * Sorry, I don't have microphone. Don't try to use full-duplex mode.
 * It wouldn't work.
 */
#define CS_TIMEOUT		90000

#define CS4231_MIN_BUF_SZ	(16*1024)
#define CS4231_DEFAULT_BUF_SZ	(32*1024)
#define CS4231_MAX_BUF_SZ	(64*1024)
#define CS4231_MAX_BLK_SZ	(8*1024)
#define CS4231_MAX_APC_DMA_SZ	(8*1024)


#undef CS4231_DEBUG
#ifdef CS4231_DEBUG
#define DPRINTF(x)		printf x
#else
#define DPRINTF(x)
#endif
#define CS4231_AUTO_CALIBRATION

struct cs4231_softc;

struct cs4231_channel {
	struct cs4231_softc	*parent;
	struct pcm_channel	*channel;
	struct snd_dbuf		*buffer;
	u_int32_t		format;
	u_int32_t		speed;
	u_int32_t		nextaddr;
	u_int32_t		togo;
	int			dir;
	int			locked;
};

#define CS4231_RES_MEM_MAX	4
#define CS4231_RES_IRQ_MAX	2
struct cs4231_softc {
	struct device		*sc_dev;
	int			sc_rid[CS4231_RES_MEM_MAX];
	struct resource		*sc_res[CS4231_RES_MEM_MAX];
	bus_space_handle_t	sc_regh[CS4231_RES_MEM_MAX];
	bus_space_tag_t		sc_regt[CS4231_RES_MEM_MAX];

	int			sc_irqrid[CS4231_RES_IRQ_MAX];
	struct resource		*sc_irqres[CS4231_RES_IRQ_MAX];
	void			*sc_ih[CS4231_RES_IRQ_MAX];
	bus_dma_tag_t		sc_dmat[CS4231_RES_IRQ_MAX];
	int			sc_burst;

	u_int32_t		sc_bufsz;
	struct cs4231_channel	sc_pch;
	struct cs4231_channel	sc_rch;
	int			sc_enabled;
	int			sc_nmres;
	int			sc_nires;
	int			sc_codecv;
	int			sc_chipvid;
	int			sc_flags;
#define CS4231_SBUS		0x01
#define CS4231_EBUS		0x02

	struct mtx		*sc_lock;
};

struct mix_table {
	u_int32_t	reg:8;
	u_int32_t	bits:8;
	u_int32_t	mute:8;
	u_int32_t	shift:4;
	u_int32_t	neg:1;
	u_int32_t	avail:1;
	u_int32_t	recdev:1;
};

static int	cs4231_bus_probe(device_t);
static int	cs4231_sbus_attach(device_t);
static int	cs4231_ebus_attach(device_t);
static int	cs4231_attach_common(struct cs4231_softc *);
static int	cs4231_bus_detach(device_t);
static int	cs4231_bus_suspend(device_t);
static int	cs4231_bus_resume(device_t);
static void	cs4231_getversion(struct cs4231_softc *);
static void	cs4231_free_resource(struct cs4231_softc *);
static void	cs4231_ebdma_reset(struct cs4231_softc *);
static void	cs4231_power_reset(struct cs4231_softc *, int);
static int	cs4231_enable(struct cs4231_softc *, int);
static void	cs4231_disable(struct cs4231_softc *);
static void	cs4231_write(struct cs4231_softc *, u_int8_t, u_int8_t);
static u_int8_t cs4231_read(struct cs4231_softc *, u_int8_t);
static void	cs4231_sbus_intr(void *);
static void	cs4231_ebus_pintr(void *arg);
static void	cs4231_ebus_cintr(void *arg);
static int	cs4231_mixer_init(struct snd_mixer *);
static void	cs4231_mixer_set_value(struct cs4231_softc *,
    const struct mix_table *, u_int8_t);
static int	cs4231_mixer_set(struct snd_mixer *, u_int32_t, u_int32_t,
    u_int32_t);
static u_int32_t	cs4231_mixer_setrecsrc(struct snd_mixer *, u_int32_t);
static void	*cs4231_chan_init(kobj_t, void *, struct snd_dbuf *,
    struct pcm_channel *, int);
static int	cs4231_chan_setformat(kobj_t, void *, u_int32_t);
static u_int32_t	cs4231_chan_setspeed(kobj_t, void *, u_int32_t);
static void	cs4231_chan_fs(struct cs4231_softc *, int, u_int8_t);
static u_int32_t	cs4231_chan_setblocksize(kobj_t, void *, u_int32_t);
static int	cs4231_chan_trigger(kobj_t, void *, int);
static u_int32_t	cs4231_chan_getptr(kobj_t, void *);
static struct pcmchan_caps *
    cs4231_chan_getcaps(kobj_t, void *);
static void	cs4231_trigger(struct cs4231_channel *);
static void	cs4231_apcdma_trigger(struct cs4231_softc *,
    struct cs4231_channel *);
static void	cs4231_ebdma_trigger(struct cs4231_softc *,
    struct cs4231_channel *);
static void	cs4231_halt(struct cs4231_channel *);

#define CS4231_LOCK(sc)		snd_mtxlock(sc->sc_lock)
#define CS4231_UNLOCK(sc)	snd_mtxunlock(sc->sc_lock)
#define CS4231_LOCK_ASSERT(sc)	snd_mtxassert(sc->sc_lock)

#define CS_WRITE(sc,r,v)	\
    bus_space_write_1((sc)->sc_regt[0], (sc)->sc_regh[0], (r) << 2, (v))
#define CS_READ(sc,r)		\
    bus_space_read_1((sc)->sc_regt[0], (sc)->sc_regh[0], (r) << 2)

#define APC_WRITE(sc,r,v)	\
    bus_space_write_4(sc->sc_regt[0], sc->sc_regh[0], r, v)
#define APC_READ(sc,r)		\
    bus_space_read_4(sc->sc_regt[0], sc->sc_regh[0], r)

#define EBDMA_P_WRITE(sc,r,v)	\
    bus_space_write_4((sc)->sc_regt[1], (sc)->sc_regh[1], (r), (v))
#define EBDMA_P_READ(sc,r)	\
    bus_space_read_4((sc)->sc_regt[1], (sc)->sc_regh[1], (r))

#define EBDMA_C_WRITE(sc,r,v)	\
    bus_space_write_4((sc)->sc_regt[2], (sc)->sc_regh[2], (r), (v))
#define EBDMA_C_READ(sc,r)	\
    bus_space_read_4((sc)->sc_regt[2], (sc)->sc_regh[2], (r))

#define AUXIO_CODEC		0x00
#define AUXIO_WRITE(sc,r,v)	\
    bus_space_write_4((sc)->sc_regt[3], (sc)->sc_regh[3], (r), (v))
#define AUXIO_READ(sc,r)	\
    bus_space_read_4((sc)->sc_regt[3], (sc)->sc_regh[3], (r))

#define CODEC_WARM_RESET	0
#define CODEC_COLD_RESET	1

/* SBus */
static device_method_t cs4231_sbus_methods[] = {
	DEVMETHOD(device_probe,		cs4231_bus_probe),
	DEVMETHOD(device_attach,	cs4231_sbus_attach),
	DEVMETHOD(device_detach,	cs4231_bus_detach),
	DEVMETHOD(device_suspend,	cs4231_bus_suspend),
	DEVMETHOD(device_resume,	cs4231_bus_resume),
	{0, 0}
};

static driver_t cs4231_sbus_driver = {
	"pcm",
	cs4231_sbus_methods,
	PCM_SOFTC_SIZE
};

DRIVER_MODULE(snd_audiocs, sbus, cs4231_sbus_driver, pcm_devclass, 0, 0);

/* EBus */
static device_method_t cs4231_ebus_methods[] = {
	DEVMETHOD(device_probe,		cs4231_bus_probe),
	DEVMETHOD(device_attach,	cs4231_ebus_attach),
	DEVMETHOD(device_detach,	cs4231_bus_detach),
	DEVMETHOD(device_suspend,	cs4231_bus_suspend),
	DEVMETHOD(device_resume,	cs4231_bus_resume),
	{0, 0}
};

static driver_t cs4231_ebus_driver = {
	"pcm",
	cs4231_ebus_methods,
	PCM_SOFTC_SIZE
};

DRIVER_MODULE(snd_audiocs, ebus, cs4231_ebus_driver, pcm_devclass, 0, 0);
MODULE_DEPEND(snd_audiocs, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
MODULE_VERSION(snd_audiocs, 1);


static u_int32_t cs4231_fmt[] = {
	SND_FORMAT(AFMT_U8, 1, 0),
	SND_FORMAT(AFMT_U8, 2, 0),
	SND_FORMAT(AFMT_MU_LAW, 1, 0),
	SND_FORMAT(AFMT_MU_LAW, 2, 0),
	SND_FORMAT(AFMT_A_LAW, 1, 0),
	SND_FORMAT(AFMT_A_LAW, 2, 0),
	SND_FORMAT(AFMT_IMA_ADPCM, 1, 0),
	SND_FORMAT(AFMT_IMA_ADPCM, 2, 0),
	SND_FORMAT(AFMT_S16_LE, 1, 0),
	SND_FORMAT(AFMT_S16_LE, 2, 0),
	SND_FORMAT(AFMT_S16_BE, 1, 0),
	SND_FORMAT(AFMT_S16_BE, 2, 0),
	0
};

static struct pcmchan_caps cs4231_caps = {5510, 48000, cs4231_fmt, 0};

/*
 * sound(4) channel interface
 */
static kobj_method_t cs4231_chan_methods[] = {
	KOBJMETHOD(channel_init,		cs4231_chan_init),
	KOBJMETHOD(channel_setformat,		cs4231_chan_setformat),
	KOBJMETHOD(channel_setspeed,		cs4231_chan_setspeed),
	KOBJMETHOD(channel_setblocksize,	cs4231_chan_setblocksize),
	KOBJMETHOD(channel_trigger,		cs4231_chan_trigger),
	KOBJMETHOD(channel_getptr,		cs4231_chan_getptr),
	KOBJMETHOD(channel_getcaps,		cs4231_chan_getcaps),
	KOBJMETHOD_END
};
CHANNEL_DECLARE(cs4231_chan); 

/*
 * sound(4) mixer interface
 */
static kobj_method_t cs4231_mixer_methods[] = {
	KOBJMETHOD(mixer_init,		cs4231_mixer_init),
	KOBJMETHOD(mixer_set,		cs4231_mixer_set),
	KOBJMETHOD(mixer_setrecsrc,	cs4231_mixer_setrecsrc),
	KOBJMETHOD_END
};
MIXER_DECLARE(cs4231_mixer);

static int
cs4231_bus_probe(device_t dev)
{
	const char *compat, *name;

	compat = ofw_bus_get_compat(dev);
	name = ofw_bus_get_name(dev);
	if (strcmp("SUNW,CS4231", name) == 0 ||
	    (compat != NULL && strcmp("SUNW,CS4231", compat) == 0)) {
		device_set_desc(dev, "Sun Audiocs");
		return (BUS_PROBE_DEFAULT);
	}
	return (ENXIO);
}

static int
cs4231_sbus_attach(device_t dev)
{
	struct cs4231_softc *sc;
	int burst;

	sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO);
	sc->sc_dev = dev;
	/*
	 * XXX
	 * No public documentation exists on programming burst size of APCDMA.
	 */
	burst = sbus_get_burstsz(sc->sc_dev);
	if ((burst & SBUS_BURST_64))
		sc->sc_burst = 64;
	else if ((burst & SBUS_BURST_32))
		sc->sc_burst = 32;
	else if ((burst & SBUS_BURST_16))
		sc->sc_burst = 16;
	else
		sc->sc_burst = 0;
	sc->sc_flags = CS4231_SBUS;
	sc->sc_nmres = 1;
	sc->sc_nires = 1;
	return cs4231_attach_common(sc);
}

static int
cs4231_ebus_attach(device_t dev)
{
	struct cs4231_softc *sc;

	sc = malloc(sizeof(struct cs4231_softc), M_DEVBUF, M_NOWAIT | M_ZERO);
	if (sc == NULL) {
		device_printf(dev, "cannot allocate softc\n");
		return (ENOMEM);
	}
	sc->sc_dev = dev;
	sc->sc_burst = EBDCSR_BURST_1;
	sc->sc_nmres = CS4231_RES_MEM_MAX;
	sc->sc_nires = CS4231_RES_IRQ_MAX;
	sc->sc_flags = CS4231_EBUS;
	return cs4231_attach_common(sc);
}

static int
cs4231_attach_common(struct cs4231_softc *sc)
{
	char status[SND_STATUSLEN];
	driver_intr_t *ihandler;
	int i;

	sc->sc_lock = snd_mtxcreate(device_get_nameunit(sc->sc_dev),
	    "snd_cs4231 softc");

	for (i = 0; i < sc->sc_nmres; i++) {
		sc->sc_rid[i] = i;
		if ((sc->sc_res[i] = bus_alloc_resource_any(sc->sc_dev,
		    SYS_RES_MEMORY, &sc->sc_rid[i], RF_ACTIVE)) == NULL) {
			device_printf(sc->sc_dev,
			    "cannot map register %d\n", i);
			goto fail;
		}
		sc->sc_regt[i] = rman_get_bustag(sc->sc_res[i]);
		sc->sc_regh[i] = rman_get_bushandle(sc->sc_res[i]);
	}
	for (i = 0; i < sc->sc_nires; i++) {
		sc->sc_irqrid[i] = i;
		if ((sc->sc_irqres[i] = bus_alloc_resource_any(sc->sc_dev,
		    SYS_RES_IRQ, &sc->sc_irqrid[i], RF_SHAREABLE | RF_ACTIVE))
		    == NULL) {
			if ((sc->sc_flags & CS4231_SBUS) != 0)
				device_printf(sc->sc_dev,
				    "cannot allocate interrupt\n");
			else
				device_printf(sc->sc_dev, "cannot allocate %s "
				    "interrupt\n", i == 0 ? "capture" :
				    "playback");
			goto fail;
		}
	}

	ihandler = cs4231_sbus_intr;
	for (i = 0; i < sc->sc_nires; i++) {
		if ((sc->sc_flags & CS4231_EBUS) != 0) {
			if (i == 0)
				ihandler = cs4231_ebus_cintr;
			else
				ihandler = cs4231_ebus_pintr;
		}
		if (snd_setup_intr(sc->sc_dev, sc->sc_irqres[i], INTR_MPSAFE,
		    ihandler, sc, &sc->sc_ih[i])) {
			if ((sc->sc_flags & CS4231_SBUS) != 0)
				device_printf(sc->sc_dev,
				    "cannot set up interrupt\n");
			else
				device_printf(sc->sc_dev, "cannot set up %s "
				    " interrupt\n", i == 0 ? "capture" :
				    "playback");
			goto fail;
		}
	}

	sc->sc_bufsz = pcm_getbuffersize(sc->sc_dev, CS4231_MIN_BUF_SZ,
	    CS4231_DEFAULT_BUF_SZ, CS4231_MAX_BUF_SZ);
	for (i = 0; i < sc->sc_nires; i++) {
		if (bus_dma_tag_create(
		    bus_get_dma_tag(sc->sc_dev),/* parent */
		    64, 0,			/* alignment, boundary */
		    BUS_SPACE_MAXADDR_32BIT,	/* lowaddr */
		    BUS_SPACE_MAXADDR,		/* highaddr */
		    NULL, NULL,			/* filtfunc, filtfuncarg */
		    sc->sc_bufsz,		/* maxsize */
		    1,				/* nsegments */
		    sc->sc_bufsz,		/* maxsegsz */
		    BUS_DMA_ALLOCNOW,		/* flags */
		    NULL,			/* lockfunc */
		    NULL,			/* lockfuncarg */
		    &sc->sc_dmat[i])) {
			if ((sc->sc_flags & CS4231_SBUS) != 0)
				device_printf(sc->sc_dev,
				    "cannot allocate DMA tag\n");
			else
				device_printf(sc->sc_dev, "cannot allocate %s "
				    "DMA tag\n", i == 0 ? "capture" :
				    "playback");
			goto fail;
		}
	}
	cs4231_enable(sc, CODEC_WARM_RESET);
	cs4231_getversion(sc);
	if (mixer_init(sc->sc_dev, &cs4231_mixer_class, sc) != 0)
		goto fail;
	if (pcm_register(sc->sc_dev, sc, 1, 1)) {
		device_printf(sc->sc_dev, "cannot register to pcm\n");
		goto fail;
	}
	if (pcm_addchan(sc->sc_dev, PCMDIR_REC, &cs4231_chan_class, sc) != 0)
		goto chan_fail;
	if (pcm_addchan(sc->sc_dev, PCMDIR_PLAY, &cs4231_chan_class, sc) != 0)
		goto chan_fail;
	if ((sc->sc_flags & CS4231_SBUS) != 0)
		snprintf(status, SND_STATUSLEN, "at mem 0x%lx irq %ld bufsz %u",
		    rman_get_start(sc->sc_res[0]),
		    rman_get_start(sc->sc_irqres[0]), sc->sc_bufsz);
	else
		snprintf(status, SND_STATUSLEN, "at io 0x%lx 0x%lx 0x%lx 0x%lx "
		    "irq %ld %ld bufsz %u", rman_get_start(sc->sc_res[0]),
		    rman_get_start(sc->sc_res[1]),
		    rman_get_start(sc->sc_res[2]),
		    rman_get_start(sc->sc_res[3]),
		    rman_get_start(sc->sc_irqres[0]),
		    rman_get_start(sc->sc_irqres[1]), sc->sc_bufsz);
	pcm_setstatus(sc->sc_dev, status);
	return (0);

chan_fail:
	pcm_unregister(sc->sc_dev);
fail:
	cs4231_free_resource(sc);
	return (ENXIO);
}

static int
cs4231_bus_detach(device_t dev)
{
	struct cs4231_softc *sc;
	struct cs4231_channel *pch, *rch;
	int error;

	sc = pcm_getdevinfo(dev);
	CS4231_LOCK(sc);
	pch = &sc->sc_pch;
	rch = &sc->sc_pch;
	if (pch->locked || rch->locked) {
		CS4231_UNLOCK(sc);
		return (EBUSY);
	}
	/*
	 * Since EBDMA requires valid DMA buffer to drain its FIFO, we need
	 * real DMA buffer for draining.
	 */
	if ((sc->sc_flags & CS4231_EBUS) != 0)
		cs4231_ebdma_reset(sc);
	CS4231_UNLOCK(sc);
	error = pcm_unregister(dev);
	if (error)
		return (error);
	cs4231_free_resource(sc);
	return (0);
}

static int
cs4231_bus_suspend(device_t dev)
{

	return (ENXIO);
}

static int
cs4231_bus_resume(device_t dev)
{

	return (ENXIO);
}

static void
cs4231_getversion(struct cs4231_softc *sc)
{
	u_int8_t v;

	v = cs4231_read(sc, CS_MISC_INFO);
	sc->sc_codecv = v & CS_CODEC_ID_MASK;
	v = cs4231_read(sc, CS_VERSION_ID);
	v &= (CS_VERSION_NUMBER | CS_VERSION_CHIPID);
	sc->sc_chipvid = v;
	switch(v) {
		case 0x80:
			device_printf(sc->sc_dev, "<CS4231 Codec Id. %d>\n",
			    sc->sc_codecv);
			break;
		case 0xa0:
			device_printf(sc->sc_dev, "<CS4231A Codec Id. %d>\n",
			    sc->sc_codecv);
			break;
		case 0x82:
			device_printf(sc->sc_dev, "<CS4232 Codec Id. %d>\n",
			    sc->sc_codecv);
			break;
		default:
			device_printf(sc->sc_dev,
			    "<Unknown 0x%x Codec Id. %d\n", v, sc->sc_codecv);
			break;
	}
}

static void
cs4231_ebdma_reset(struct cs4231_softc *sc)
{
	int i;

	/* playback */
	EBDMA_P_WRITE(sc, EBDMA_DCSR,
	    EBDMA_P_READ(sc, EBDMA_DCSR) & ~(EBDCSR_INTEN | EBDCSR_NEXTEN));
	EBDMA_P_WRITE(sc, EBDMA_DCSR, EBDCSR_RESET);
	for (i = CS_TIMEOUT;
	    i && EBDMA_P_READ(sc, EBDMA_DCSR) & EBDCSR_DRAIN; i--)
		DELAY(1);
	if (i == 0)
		device_printf(sc->sc_dev,
		    "timeout waiting for playback DMA reset\n");
	EBDMA_P_WRITE(sc, EBDMA_DCSR, sc->sc_burst);
	/* capture */
	EBDMA_C_WRITE(sc, EBDMA_DCSR,
	    EBDMA_C_READ(sc, EBDMA_DCSR) & ~(EBDCSR_INTEN | EBDCSR_NEXTEN));
	EBDMA_C_WRITE(sc, EBDMA_DCSR, EBDCSR_RESET);
	for (i = CS_TIMEOUT;
	    i && EBDMA_C_READ(sc, EBDMA_DCSR) & EBDCSR_DRAIN; i--)
		DELAY(1);
	if (i == 0)
		device_printf(sc->sc_dev,
		    "timeout waiting for capture DMA reset\n");
	EBDMA_C_WRITE(sc, EBDMA_DCSR, sc->sc_burst);
}

static void
cs4231_power_reset(struct cs4231_softc *sc, int how)
{
	u_int32_t v;
	int i;

	if ((sc->sc_flags & CS4231_SBUS) != 0) {
		APC_WRITE(sc, APC_CSR, APC_CSR_RESET);
		DELAY(10);
		APC_WRITE(sc, APC_CSR, 0);
		DELAY(10);
		APC_WRITE(sc,
		    APC_CSR, APC_READ(sc, APC_CSR) | APC_CSR_CODEC_RESET);
		DELAY(20);
		APC_WRITE(sc,
		    APC_CSR, APC_READ(sc, APC_CSR) & (~APC_CSR_CODEC_RESET));
	} else {
		v = AUXIO_READ(sc, AUXIO_CODEC);
		if (how == CODEC_WARM_RESET && v != 0) {
			AUXIO_WRITE(sc, AUXIO_CODEC, 0);
			DELAY(20);
		} else if (how == CODEC_COLD_RESET){
			AUXIO_WRITE(sc, AUXIO_CODEC, 1);
			DELAY(20);
			AUXIO_WRITE(sc, AUXIO_CODEC, 0);
			DELAY(20);
		}
		cs4231_ebdma_reset(sc);
	}

	for (i = CS_TIMEOUT;
	    i && CS_READ(sc, CS4231_IADDR) == CS_IN_INIT; i--)
		DELAY(10);
	if (i == 0)
		device_printf(sc->sc_dev, "timeout waiting for reset\n");

	/* turn on cs4231 mode */
	cs4231_write(sc, CS_MISC_INFO,
	    cs4231_read(sc, CS_MISC_INFO) | CS_MODE2);
	/* enable interrupts & clear CSR */
        cs4231_write(sc, CS_PIN_CONTROL,
            cs4231_read(sc, CS_PIN_CONTROL) | INTERRUPT_ENABLE);
	CS_WRITE(sc, CS4231_STATUS, 0);
	/* enable DAC output */
	cs4231_write(sc, CS_LEFT_OUTPUT_CONTROL,
	    cs4231_read(sc, CS_LEFT_OUTPUT_CONTROL) & ~OUTPUT_MUTE);
	cs4231_write(sc, CS_RIGHT_OUTPUT_CONTROL,
	    cs4231_read(sc, CS_RIGHT_OUTPUT_CONTROL) & ~OUTPUT_MUTE);
	/* mute AUX1 since it generates noises */
	cs4231_write(sc, CS_LEFT_AUX1_CONTROL,
	    cs4231_read(sc, CS_LEFT_AUX1_CONTROL) | AUX_INPUT_MUTE);
	cs4231_write(sc, CS_RIGHT_AUX1_CONTROL,
	    cs4231_read(sc, CS_RIGHT_AUX1_CONTROL) | AUX_INPUT_MUTE);
	/* protect buffer underrun and set output level to 0dB */
	cs4231_write(sc, CS_ALT_FEATURE1,
	    cs4231_read(sc, CS_ALT_FEATURE1) | CS_DAC_ZERO | CS_OUTPUT_LVL);
	/* enable high pass filter, dual xtal was disabled due to noises */
	cs4231_write(sc, CS_ALT_FEATURE2,
	    cs4231_read(sc, CS_ALT_FEATURE2) | CS_HPF_ENABLE);
}

static int
cs4231_enable(struct cs4231_softc *sc, int how)
{
	cs4231_power_reset(sc, how);
	sc->sc_enabled = 1;
        return (0);
}

static void
cs4231_disable(struct cs4231_softc *sc)
{
	u_int8_t v;

	CS4231_LOCK_ASSERT(sc);

	if (sc->sc_enabled == 0)
		return;
	sc->sc_enabled = 0;
	CS4231_UNLOCK(sc);
	cs4231_halt(&sc->sc_pch);
	cs4231_halt(&sc->sc_rch);
	CS4231_LOCK(sc);
	v = cs4231_read(sc, CS_PIN_CONTROL) & ~INTERRUPT_ENABLE;
	cs4231_write(sc, CS_PIN_CONTROL, v);

	if ((sc->sc_flags & CS4231_SBUS) != 0) {
		APC_WRITE(sc, APC_CSR, APC_CSR_RESET);
		DELAY(10);
		APC_WRITE(sc, APC_CSR, 0);
		DELAY(10);
	} else
		cs4231_ebdma_reset(sc);
}

static void
cs4231_free_resource(struct cs4231_softc *sc)
{
	int i;

	CS4231_LOCK(sc);
	cs4231_disable(sc);
	CS4231_UNLOCK(sc);
	for (i = 0; i < sc->sc_nires; i++) {
		if (sc->sc_irqres[i]) {
			if (sc->sc_ih[i]) {
				bus_teardown_intr(sc->sc_dev, sc->sc_irqres[i],
				    sc->sc_ih[i]);
				sc->sc_ih[i] = NULL;
			}
			bus_release_resource(sc->sc_dev, SYS_RES_IRQ,
			    sc->sc_irqrid[i], sc->sc_irqres[i]);
			sc->sc_irqres[i] = NULL;
		}
	}
	for (i = 0; i < sc->sc_nires; i++) {
		if (sc->sc_dmat[i])
			bus_dma_tag_destroy(sc->sc_dmat[i]);
	}
	for (i = 0; i < sc->sc_nmres; i++) {
		if (sc->sc_res[i])
			bus_release_resource(sc->sc_dev, SYS_RES_MEMORY,
			    sc->sc_rid[i], sc->sc_res[i]);
	}
	snd_mtxfree(sc->sc_lock);
	free(sc, M_DEVBUF);
}

static void
cs4231_write(struct cs4231_softc *sc, u_int8_t r, u_int8_t v)
{
	CS_WRITE(sc, CS4231_IADDR, r);
	CS_WRITE(sc, CS4231_IDATA, v);
}

static u_int8_t
cs4231_read(struct cs4231_softc *sc, u_int8_t r)
{
	CS_WRITE(sc, CS4231_IADDR, r);
	return (CS_READ(sc, CS4231_IDATA));
}

static void
cs4231_sbus_intr(void *arg)
{
	struct cs4231_softc *sc;
	struct cs4231_channel *pch, *rch;
	u_int32_t csr;
	u_int8_t status;

	sc = arg;
	CS4231_LOCK(sc);

	csr = APC_READ(sc, APC_CSR);
	if ((csr & APC_CSR_GI) == 0) {
		CS4231_UNLOCK(sc);
		return;
	}
	APC_WRITE(sc, APC_CSR, csr);

	if ((csr & APC_CSR_EIE) && (csr & APC_CSR_EI)) {
		status = cs4231_read(sc, CS_TEST_AND_INIT);
		device_printf(sc->sc_dev,
		    "apc error interrupt : stat = 0x%x\n", status);
	}

	pch = rch = NULL;
	if ((csr & APC_CSR_PMIE) && (csr & APC_CSR_PMI)) {
		u_long nextaddr, saddr;
		u_int32_t togo;

		pch = &sc->sc_pch;
		togo = pch->togo;
		saddr = sndbuf_getbufaddr(pch->buffer);
		nextaddr = pch->nextaddr + togo;
		if (nextaddr >=  saddr + sndbuf_getsize(pch->buffer))
			nextaddr = saddr;
		APC_WRITE(sc, APC_PNVA, nextaddr);
		APC_WRITE(sc, APC_PNC, togo);
		pch->nextaddr = nextaddr;
	}

	if ((csr & APC_CSR_CIE) && (csr & APC_CSR_CI) && (csr & APC_CSR_CD)) {
		u_long nextaddr, saddr;
		u_int32_t togo;

		rch = &sc->sc_rch;
		togo = rch->togo;
		saddr = sndbuf_getbufaddr(rch->buffer);
		nextaddr = rch->nextaddr + togo;
		if (nextaddr >= saddr + sndbuf_getsize(rch->buffer))
			nextaddr = saddr; 
		APC_WRITE(sc, APC_CNVA, nextaddr);
		APC_WRITE(sc, APC_CNC, togo);
		rch->nextaddr = nextaddr;
	}
	CS4231_UNLOCK(sc);
	if (pch)
		chn_intr(pch->channel);
	if (rch)
		chn_intr(rch->channel);
}

/* playback interrupt handler */
static void
cs4231_ebus_pintr(void *arg)
{
	struct cs4231_softc *sc;
	struct cs4231_channel *ch;
	u_int32_t csr;
	u_int8_t status;

	sc = arg;
	CS4231_LOCK(sc);

	csr = EBDMA_P_READ(sc, EBDMA_DCSR);
	if ((csr & EBDCSR_INT) == 0) {
		CS4231_UNLOCK(sc);
		return;
	}

	if ((csr & EBDCSR_ERR)) {
		status = cs4231_read(sc, CS_TEST_AND_INIT);
		device_printf(sc->sc_dev,
		    "ebdma error interrupt : stat = 0x%x\n", status);
	}
	EBDMA_P_WRITE(sc, EBDMA_DCSR, csr | EBDCSR_TC);

	ch = NULL;
	if (csr & EBDCSR_TC) {
		u_long nextaddr, saddr;
		u_int32_t togo;

		ch = &sc->sc_pch;
		togo = ch->togo;
		saddr = sndbuf_getbufaddr(ch->buffer);
		nextaddr = ch->nextaddr + togo;
		if (nextaddr >=  saddr + sndbuf_getsize(ch->buffer))
			nextaddr = saddr;
		/*
		 * EBDMA_DCNT is loaded automatically
		 * EBDMA_P_WRITE(sc, EBDMA_DCNT, togo);
		 */
		EBDMA_P_WRITE(sc, EBDMA_DADDR, nextaddr);
		ch->nextaddr = nextaddr;
	}
	CS4231_UNLOCK(sc);
	if (ch)
		chn_intr(ch->channel);
}

/* capture interrupt handler */
static void
cs4231_ebus_cintr(void *arg)
{
	struct cs4231_softc *sc;
	struct cs4231_channel *ch;
	u_int32_t csr;
	u_int8_t status;

	sc = arg;
	CS4231_LOCK(sc);

	csr = EBDMA_C_READ(sc, EBDMA_DCSR);
	if ((csr & EBDCSR_INT) == 0) {
		CS4231_UNLOCK(sc);
		return;
	}
	if ((csr & EBDCSR_ERR)) {
		status = cs4231_read(sc, CS_TEST_AND_INIT);
		device_printf(sc->sc_dev,
		    "dma error interrupt : stat = 0x%x\n", status);
	}
	EBDMA_C_WRITE(sc, EBDMA_DCSR, csr | EBDCSR_TC);

	ch = NULL;
	if (csr & EBDCSR_TC) {
		u_long nextaddr, saddr;
		u_int32_t togo;

		ch = &sc->sc_rch;
		togo = ch->togo;
		saddr = sndbuf_getbufaddr(ch->buffer);
		nextaddr = ch->nextaddr + togo;
		if (nextaddr >= saddr + sndbuf_getblksz(ch->buffer))
			nextaddr = saddr; 
		/*
		 * EBDMA_DCNT is loaded automatically
		 * EBDMA_C_WRITE(sc, EBDMA_DCNT, togo);
		 */
		EBDMA_C_WRITE(sc, EBDMA_DADDR, nextaddr);
		ch->nextaddr = nextaddr;
	}
	CS4231_UNLOCK(sc);
	if (ch)
		chn_intr(ch->channel);
}

static const struct mix_table cs4231_mix_table[SOUND_MIXER_NRDEVICES][2] = {
	[SOUND_MIXER_PCM] = {
		{ CS_LEFT_OUTPUT_CONTROL,	6, OUTPUT_MUTE, 0, 1, 1, 0 },
		{ CS_RIGHT_OUTPUT_CONTROL,	6, OUTPUT_MUTE, 0, 1, 1, 0 }
	},
	[SOUND_MIXER_SPEAKER] = {
		{ CS_MONO_IO_CONTROL,		4, MONO_OUTPUT_MUTE, 0, 1, 1, 0 },
		{ CS_REG_NONE,			0, 0, 0, 0, 1, 0 }
	},
	[SOUND_MIXER_LINE] = {
		{ CS_LEFT_LINE_CONTROL,		5, LINE_INPUT_MUTE, 0, 1, 1, 1 },
		{ CS_RIGHT_LINE_CONTROL,	5, LINE_INPUT_MUTE, 0, 1, 1, 1 }
	},
	/*
	 * AUX1 : removed intentionally since it generates noises
	 * AUX2 : Ultra1/Ultra2 has no internal CD-ROM audio in
	 */
	[SOUND_MIXER_CD] = {
		{ CS_LEFT_AUX2_CONTROL,		5, LINE_INPUT_MUTE, 0, 1, 1, 1 },
		{ CS_RIGHT_AUX2_CONTROL,	5, LINE_INPUT_MUTE, 0, 1, 1, 1 }
	},
	[SOUND_MIXER_MIC] = {
		{ CS_LEFT_INPUT_CONTROL,	4, 0, 0, 0, 1, 1 },
		{ CS_RIGHT_INPUT_CONTROL,	4, 0, 0, 0, 1, 1 }
	},
	[SOUND_MIXER_IGAIN] = {
		{ CS_LEFT_INPUT_CONTROL,	4, 0, 0, 1, 0 },
		{ CS_RIGHT_INPUT_CONTROL,	4, 0, 0, 1, 0 }
	}
};

static int
cs4231_mixer_init(struct snd_mixer *m)
{
	u_int32_t v;
	int i;

	v = 0;
	for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
		if (cs4231_mix_table[i][0].avail != 0)
			v |= (1 << i);
	mix_setdevs(m, v);
	v = 0;
	for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
		if (cs4231_mix_table[i][0].recdev != 0)
			v |= (1 << i);
	mix_setrecdevs(m, v);
	return (0);
}

static void
cs4231_mixer_set_value(struct cs4231_softc *sc,  const struct mix_table *mt,
    u_int8_t v)
{
	u_int8_t mask, reg;
	u_int8_t old, shift, val;

	if (mt->avail == 0 || mt->reg == CS_REG_NONE)
		return;
	reg = mt->reg;
	if (mt->neg != 0)
		val = 100 - v;
	else
		val = v;
	mask = (1 << mt->bits) - 1;
	val = ((val * mask) + 50) / 100;
	shift = mt->shift;
	val <<= shift;
	if (v == 0)
		val |= mt->mute;
	old = cs4231_read(sc, reg);
	old &= ~(mt->mute | (mask << shift));
	val |= old;
	if (reg == CS_LEFT_INPUT_CONTROL || reg == CS_RIGHT_INPUT_CONTROL) {
		if ((val & (mask << shift)) != 0)
			val |= ADC_INPUT_GAIN_ENABLE;
		else
			val &= ~ADC_INPUT_GAIN_ENABLE;
	}
	cs4231_write(sc, reg, val);	
}

static int
cs4231_mixer_set(struct snd_mixer *m, u_int32_t dev, u_int32_t left,
    u_int32_t right)
{
	struct cs4231_softc *sc;

	sc = mix_getdevinfo(m);
	CS4231_LOCK(sc);
	cs4231_mixer_set_value(sc, &cs4231_mix_table[dev][0], left);
	cs4231_mixer_set_value(sc, &cs4231_mix_table[dev][1], right);
	CS4231_UNLOCK(sc);

	return (left | (right << 8));
}

static u_int32_t
cs4231_mixer_setrecsrc(struct snd_mixer *m, u_int32_t src)
{
	struct cs4231_softc *sc;
	u_int8_t	v;

	sc = mix_getdevinfo(m);
	switch (src) {
	case SOUND_MASK_LINE:
		v = CS_IN_LINE;
		break;

	case SOUND_MASK_CD:
		v = CS_IN_DAC;
		break;

	case SOUND_MASK_MIC:
	default:
		v = CS_IN_MIC;
		src = SOUND_MASK_MIC;
		break;
	}
	CS4231_LOCK(sc);
	cs4231_write(sc, CS_LEFT_INPUT_CONTROL,
	    (cs4231_read(sc, CS_LEFT_INPUT_CONTROL) & CS_IN_MASK) | v);
	cs4231_write(sc, CS_RIGHT_INPUT_CONTROL,
	    (cs4231_read(sc, CS_RIGHT_INPUT_CONTROL) & CS_IN_MASK) | v);
	CS4231_UNLOCK(sc);

	return (src);
}

static void *
cs4231_chan_init(kobj_t obj, void *dev, struct snd_dbuf *b,
    struct pcm_channel *c, int dir)
{
	struct cs4231_softc *sc;
	struct cs4231_channel *ch;
	bus_dma_tag_t dmat;

	sc = dev;
	ch = (dir == PCMDIR_PLAY) ? &sc->sc_pch : &sc->sc_rch;
	ch->parent = sc;
	ch->channel = c;
	ch->dir = dir;
	ch->buffer = b;
	if ((sc->sc_flags & CS4231_SBUS) != 0)
		dmat = sc->sc_dmat[0];
	else {
		if (dir == PCMDIR_PLAY)
			dmat = sc->sc_dmat[1];
		else
			dmat = sc->sc_dmat[0];
	}
	if (sndbuf_alloc(ch->buffer, dmat, 0, sc->sc_bufsz) != 0)
		return (NULL);
	DPRINTF(("%s channel addr: 0x%lx\n", dir == PCMDIR_PLAY ? "playback" :
	    "capture", sndbuf_getbufaddr(ch->buffer)));

	return (ch);
}

static int
cs4231_chan_setformat(kobj_t obj, void *data, u_int32_t format)
{
	struct cs4231_softc *sc;
	struct cs4231_channel *ch;
	u_int32_t encoding;
	u_int8_t fs, v;

	ch = data;
	sc = ch->parent;

	CS4231_LOCK(sc);
	if (ch->format == format) {
		CS4231_UNLOCK(sc);
		return (0);
	}

	encoding = AFMT_ENCODING(format);
	fs = 0;
	switch (encoding) {
	case AFMT_U8:
		fs = CS_AFMT_U8;
		break;
	case AFMT_MU_LAW:
		fs = CS_AFMT_MU_LAW;
		break;
	case AFMT_S16_LE:
		fs = CS_AFMT_S16_LE;
		break;
	case AFMT_A_LAW:
		fs = CS_AFMT_A_LAW;
		break;
	case AFMT_IMA_ADPCM:
		fs = CS_AFMT_IMA_ADPCM;
		break;
	case AFMT_S16_BE:
		fs = CS_AFMT_S16_BE;
		break;
	default:
		fs = CS_AFMT_U8;
		format = AFMT_U8;
		break;
	}

	if (AFMT_CHANNEL(format) > 1)
		fs |= CS_AFMT_STEREO;
	
	DPRINTF(("FORMAT: %s : 0x%x\n", ch->dir == PCMDIR_PLAY ? "playback" :
	    "capture", format));
	v = cs4231_read(sc, CS_CLOCK_DATA_FORMAT);
	v &= CS_CLOCK_DATA_FORMAT_MASK;
	fs |= v;
	cs4231_chan_fs(sc, ch->dir, fs);
	ch->format = format;
	CS4231_UNLOCK(sc);

	return (0);
}

static u_int32_t
cs4231_chan_setspeed(kobj_t obj, void *data, u_int32_t speed)
{
	typedef struct {
		u_int32_t speed;
		u_int8_t bits;
	} speed_struct;

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

	struct cs4231_softc *sc;
	struct cs4231_channel *ch;
	int i, n, sel;
	u_int8_t fs;

	ch = data;
	sc = ch->parent;
	CS4231_LOCK(sc);
	if (ch->speed == speed) {
		CS4231_UNLOCK(sc);
		return (speed);
	}
	n = sizeof(speed_table) / sizeof(speed_struct);

	for (i = 1, sel =0; i < n - 1; i++)
		if (abs(speed - speed_table[i].speed) <
		    abs(speed - speed_table[sel].speed))
			sel = i;	
	DPRINTF(("SPEED: %s : %dHz -> %dHz\n", ch->dir == PCMDIR_PLAY ?
	    "playback" : "capture", speed, speed_table[sel].speed));
	speed = speed_table[sel].speed;

	fs = cs4231_read(sc, CS_CLOCK_DATA_FORMAT);
	fs &= ~CS_CLOCK_DATA_FORMAT_MASK;
	fs |= speed_table[sel].bits;
	cs4231_chan_fs(sc, ch->dir, fs);
	ch->speed = speed;
	CS4231_UNLOCK(sc);

	return (speed);
}

static void
cs4231_chan_fs(struct cs4231_softc *sc, int dir, u_int8_t fs)
{
	int i, doreset;
#ifdef CS4231_AUTO_CALIBRATION
	u_int8_t v;
#endif

	CS4231_LOCK_ASSERT(sc);

	/* set autocalibration */
	doreset = 0;
#ifdef CS4231_AUTO_CALIBRATION
	v = cs4231_read(sc, CS_INTERFACE_CONFIG) | AUTO_CAL_ENABLE;
	CS_WRITE(sc, CS4231_IADDR, MODE_CHANGE_ENABLE);
	CS_WRITE(sc, CS4231_IADDR, MODE_CHANGE_ENABLE | CS_INTERFACE_CONFIG);
	CS_WRITE(sc, CS4231_IDATA, v);
#endif

	/*
	 * We always need to write CS_CLOCK_DATA_FORMAT register since
	 * the clock frequency is shared with playback/capture.
	 */
	CS_WRITE(sc, CS4231_IADDR, MODE_CHANGE_ENABLE | CS_CLOCK_DATA_FORMAT);
	CS_WRITE(sc, CS4231_IDATA, fs);
	CS_READ(sc, CS4231_IDATA);
	CS_READ(sc, CS4231_IDATA);
	for (i = CS_TIMEOUT;
	    i && CS_READ(sc, CS4231_IADDR) == CS_IN_INIT; i--)
		DELAY(10);
	if (i == 0) {
		device_printf(sc->sc_dev, "timeout setting playback speed\n");
		doreset++;
	}

	/*
	 * capture channel
	 * cs4231 doesn't allow separate fs setup for playback/capture.
	 * I believe this will break full-duplex operation.
	 */
	if (dir == PCMDIR_REC) {
		CS_WRITE(sc, CS4231_IADDR, MODE_CHANGE_ENABLE | CS_REC_FORMAT);
		CS_WRITE(sc, CS4231_IDATA, fs);
		CS_READ(sc, CS4231_IDATA);
		CS_READ(sc, CS4231_IDATA);
		for (i = CS_TIMEOUT;
		    i && CS_READ(sc, CS4231_IADDR) == CS_IN_INIT; i--)
			DELAY(10);
		if (i == 0) {
			device_printf(sc->sc_dev,
			    "timeout setting capture format\n");
			doreset++;
		}
	}

	CS_WRITE(sc, CS4231_IADDR, 0);
	for (i = CS_TIMEOUT;
	    i && CS_READ(sc, CS4231_IADDR) == CS_IN_INIT; i--)
		DELAY(10);
	if (i == 0) {
		device_printf(sc->sc_dev, "timeout waiting for !MCE\n");
		doreset++;
	}

#ifdef CS4231_AUTO_CALIBRATION
	CS_WRITE(sc, CS4231_IADDR, CS_TEST_AND_INIT);
	for (i = CS_TIMEOUT;
	    i && CS_READ(sc, CS4231_IDATA) & AUTO_CAL_IN_PROG; i--)
		DELAY(10);
	if (i == 0) {
		device_printf(sc->sc_dev,
		    "timeout waiting for autocalibration\n");
		doreset++;
	}
#endif
	if (doreset) {
		/*
		 * Maybe the last resort to avoid a dreadful message like
		 * "pcm0:play:0: play interrupt timeout, channel dead" would
		 * be hardware reset.
		 */
		device_printf(sc->sc_dev, "trying to hardware reset\n");
		cs4231_disable(sc);
		cs4231_enable(sc, CODEC_COLD_RESET);
		CS4231_UNLOCK(sc); /* XXX */
		if (mixer_reinit(sc->sc_dev) != 0) 
			device_printf(sc->sc_dev,
			    "unable to reinitialize the mixer\n");
		CS4231_LOCK(sc);
	}
}

static u_int32_t
cs4231_chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize)
{
	struct cs4231_softc *sc;
	struct cs4231_channel *ch;
	int nblks, error;

	ch = data;
	sc = ch->parent;

	if (blocksize > CS4231_MAX_BLK_SZ)
		blocksize = CS4231_MAX_BLK_SZ;
	nblks = sc->sc_bufsz / blocksize;
	error = sndbuf_resize(ch->buffer, nblks, blocksize);
	if (error != 0)
		device_printf(sc->sc_dev,
		    "unable to block size, blksz = %d, error = %d\n",
		    blocksize, error);

        return (blocksize);
}

static int
cs4231_chan_trigger(kobj_t obj, void *data, int go)
{
	struct cs4231_channel *ch;

	ch = data;
	switch (go) {
	case PCMTRIG_EMLDMAWR:
	case PCMTRIG_EMLDMARD:
		break;
	case PCMTRIG_START:
		cs4231_trigger(ch);
		break;
	case PCMTRIG_ABORT:
	case PCMTRIG_STOP:
		cs4231_halt(ch);
		break;
	default:
		break;
	}

	return (0);
}

static u_int32_t
cs4231_chan_getptr(kobj_t obj, void *data)
{
	struct cs4231_softc *sc;
	struct cs4231_channel *ch;
	u_int32_t cur, ptr, sz;

	ch = data;
	sc = ch->parent;

	CS4231_LOCK(sc);
	if ((sc->sc_flags & CS4231_SBUS) != 0)
		cur = (ch->dir == PCMDIR_PLAY) ? APC_READ(sc, APC_PVA) :
		    APC_READ(sc, APC_CVA);
	else
		cur = (ch->dir == PCMDIR_PLAY) ? EBDMA_P_READ(sc, EBDMA_DADDR) :
			EBDMA_C_READ(sc, EBDMA_DADDR);
	sz = sndbuf_getsize(ch->buffer);
	ptr = cur - sndbuf_getbufaddr(ch->buffer) + sz;
	CS4231_UNLOCK(sc);

	ptr %= sz;
	return (ptr);
}

static struct pcmchan_caps *
cs4231_chan_getcaps(kobj_t obj, void *data)
{

	return (&cs4231_caps);
}

static void
cs4231_trigger(struct cs4231_channel *ch)
{
	struct cs4231_softc *sc;

	sc = ch->parent;
	if ((sc->sc_flags & CS4231_SBUS) != 0)
		cs4231_apcdma_trigger(sc, ch);
	else
		cs4231_ebdma_trigger(sc, ch);
}

static void
cs4231_apcdma_trigger(struct cs4231_softc *sc, struct cs4231_channel *ch)
{
	u_int32_t csr, togo;
	u_int32_t nextaddr;

	CS4231_LOCK(sc);
	if (ch->locked) {
		device_printf(sc->sc_dev, "%s channel already triggered\n",
		    ch->dir == PCMDIR_PLAY ? "playback" : "capture");
		CS4231_UNLOCK(sc);
		return;
	}

	nextaddr = sndbuf_getbufaddr(ch->buffer);
	togo = sndbuf_getsize(ch->buffer) / 2;
	if (togo > CS4231_MAX_APC_DMA_SZ)
		togo = CS4231_MAX_APC_DMA_SZ;
	ch->togo = togo;
	if (ch->dir == PCMDIR_PLAY) {
		DPRINTF(("TRG: PNVA = 0x%x, togo = 0x%x\n", nextaddr, togo));

		cs4231_read(sc, CS_TEST_AND_INIT); /* clear pending error */
		csr = APC_READ(sc, APC_CSR);
		APC_WRITE(sc, APC_PNVA, nextaddr);
		APC_WRITE(sc, APC_PNC, togo);
			
		if ((csr & APC_CSR_PDMA_GO) == 0 ||
		    (csr & APC_CSR_PPAUSE) != 0) {
			APC_WRITE(sc, APC_CSR, APC_READ(sc, APC_CSR) &
			    ~(APC_CSR_PIE | APC_CSR_PPAUSE));
			APC_WRITE(sc, APC_CSR, APC_READ(sc, APC_CSR) |
			    APC_CSR_GIE | APC_CSR_PIE | APC_CSR_EIE |
			    APC_CSR_EI | APC_CSR_PMIE | APC_CSR_PDMA_GO);
			cs4231_write(sc, CS_INTERFACE_CONFIG,
			    cs4231_read(sc, CS_INTERFACE_CONFIG) |
			    PLAYBACK_ENABLE);
		}
		/* load next address */
		if (APC_READ(sc, APC_CSR) & APC_CSR_PD) {
			nextaddr += togo;
			APC_WRITE(sc, APC_PNVA, nextaddr);
			APC_WRITE(sc, APC_PNC, togo);
		}
	} else {
		DPRINTF(("TRG: CNVA = 0x%x, togo = 0x%x\n", nextaddr, togo));

		cs4231_read(sc, CS_TEST_AND_INIT); /* clear pending error */
		APC_WRITE(sc, APC_CNVA, nextaddr);
		APC_WRITE(sc, APC_CNC, togo);
		csr = APC_READ(sc, APC_CSR);
		if ((csr & APC_CSR_CDMA_GO) == 0 ||
		    (csr & APC_CSR_CPAUSE) != 0) {
			csr &= APC_CSR_CPAUSE;
			csr |= APC_CSR_GIE | APC_CSR_CMIE | APC_CSR_CIE |
			    APC_CSR_EI | APC_CSR_CDMA_GO;
			APC_WRITE(sc, APC_CSR, csr);
			cs4231_write(sc, CS_INTERFACE_CONFIG,
			    cs4231_read(sc, CS_INTERFACE_CONFIG) |
			    CAPTURE_ENABLE);
		}
		/* load next address */
		if (APC_READ(sc, APC_CSR) & APC_CSR_CD) {
			nextaddr += togo;
			APC_WRITE(sc, APC_CNVA, nextaddr);
			APC_WRITE(sc, APC_CNC, togo);
		}
	}
	ch->nextaddr = nextaddr;
	ch->locked = 1;
	CS4231_UNLOCK(sc);
}

static void
cs4231_ebdma_trigger(struct cs4231_softc *sc, struct cs4231_channel *ch)
{
	u_int32_t csr, togo;
	u_int32_t nextaddr;

	CS4231_LOCK(sc);
	if (ch->locked) {
		device_printf(sc->sc_dev, "%s channel already triggered\n",
		    ch->dir == PCMDIR_PLAY ? "playback" : "capture");
		CS4231_UNLOCK(sc);
		return;
	}

	nextaddr = sndbuf_getbufaddr(ch->buffer);
	togo = sndbuf_getsize(ch->buffer) / 2;
	if (togo % 64 == 0)
		sc->sc_burst = EBDCSR_BURST_16;
	else if (togo % 32 == 0)
		sc->sc_burst = EBDCSR_BURST_8;
	else if (togo % 16 == 0)
		sc->sc_burst = EBDCSR_BURST_4;
	else 
		sc->sc_burst = EBDCSR_BURST_1;
	ch->togo = togo;
	DPRINTF(("TRG: DNAR = 0x%x, togo = 0x%x\n", nextaddr, togo));
	if (ch->dir == PCMDIR_PLAY) {
		cs4231_read(sc, CS_TEST_AND_INIT); /* clear pending error */
		csr = EBDMA_P_READ(sc, EBDMA_DCSR);

		if (csr & EBDCSR_DMAEN) {
			EBDMA_P_WRITE(sc, EBDMA_DCNT, togo);
			EBDMA_P_WRITE(sc, EBDMA_DADDR, nextaddr);
		} else {
			EBDMA_P_WRITE(sc, EBDMA_DCSR, EBDCSR_RESET);
			EBDMA_P_WRITE(sc, EBDMA_DCSR, sc->sc_burst);
			EBDMA_P_WRITE(sc, EBDMA_DCNT, togo);
			EBDMA_P_WRITE(sc, EBDMA_DADDR, nextaddr);

			EBDMA_P_WRITE(sc, EBDMA_DCSR, sc->sc_burst |
			    EBDCSR_DMAEN | EBDCSR_INTEN | EBDCSR_CNTEN |
			    EBDCSR_NEXTEN);
			cs4231_write(sc, CS_INTERFACE_CONFIG,
			    cs4231_read(sc, CS_INTERFACE_CONFIG) |
			    PLAYBACK_ENABLE);
		}
		/* load next address */
		if (EBDMA_P_READ(sc, EBDMA_DCSR) & EBDCSR_A_LOADED) {
			nextaddr += togo;
			EBDMA_P_WRITE(sc, EBDMA_DCNT, togo);
			EBDMA_P_WRITE(sc, EBDMA_DADDR, nextaddr);
		}
	} else {
		cs4231_read(sc, CS_TEST_AND_INIT); /* clear pending error */
		csr = EBDMA_C_READ(sc, EBDMA_DCSR);

		if (csr & EBDCSR_DMAEN) {
			EBDMA_C_WRITE(sc, EBDMA_DCNT, togo);
			EBDMA_C_WRITE(sc, EBDMA_DADDR, nextaddr);
		} else {
			EBDMA_C_WRITE(sc, EBDMA_DCSR, EBDCSR_RESET);
			EBDMA_C_WRITE(sc, EBDMA_DCSR, sc->sc_burst);
			EBDMA_C_WRITE(sc, EBDMA_DCNT, togo);
			EBDMA_C_WRITE(sc, EBDMA_DADDR, nextaddr);

			EBDMA_C_WRITE(sc, EBDMA_DCSR, sc->sc_burst |
			    EBDCSR_WRITE | EBDCSR_DMAEN | EBDCSR_INTEN |
			    EBDCSR_CNTEN | EBDCSR_NEXTEN);
			cs4231_write(sc, CS_INTERFACE_CONFIG,
			    cs4231_read(sc, CS_INTERFACE_CONFIG) |
			    CAPTURE_ENABLE);
		}
		/* load next address */
		if (EBDMA_C_READ(sc, EBDMA_DCSR) & EBDCSR_A_LOADED) {
			nextaddr += togo;
			EBDMA_C_WRITE(sc, EBDMA_DCNT, togo);
			EBDMA_C_WRITE(sc, EBDMA_DADDR, nextaddr);
		}
	}
	ch->nextaddr = nextaddr;
	ch->locked = 1;
	CS4231_UNLOCK(sc);
}

static void
cs4231_halt(struct cs4231_channel *ch)
{
	struct cs4231_softc *sc;
	u_int8_t status;
	int i;

	sc = ch->parent;
	CS4231_LOCK(sc);
	if (ch->locked == 0) {
		CS4231_UNLOCK(sc);
		return;
	}

	if (ch->dir == PCMDIR_PLAY ) {
		if ((sc->sc_flags & CS4231_SBUS) != 0) {
			/* XXX Kills some capture bits */
			APC_WRITE(sc, APC_CSR, APC_READ(sc, APC_CSR) &
			    ~(APC_CSR_EI | APC_CSR_GIE | APC_CSR_PIE |
			    APC_CSR_EIE | APC_CSR_PDMA_GO | APC_CSR_PMIE));
		} else {
			EBDMA_P_WRITE(sc, EBDMA_DCSR,
			    EBDMA_P_READ(sc, EBDMA_DCSR) & ~EBDCSR_DMAEN);
		}
		/* Waiting for playback FIFO to empty */
		status = cs4231_read(sc, CS_TEST_AND_INIT);
		for (i = CS_TIMEOUT;
		    i && (status & PLAYBACK_UNDERRUN) == 0; i--) {
			DELAY(5);
			status = cs4231_read(sc, CS_TEST_AND_INIT);
		}
		if (i == 0)
			device_printf(sc->sc_dev, "timeout waiting for "
			    "playback FIFO drain\n");
		cs4231_write(sc, CS_INTERFACE_CONFIG,
		    cs4231_read(sc, CS_INTERFACE_CONFIG) & (~PLAYBACK_ENABLE));
	} else {
		if ((sc->sc_flags & CS4231_SBUS) != 0) {
			/* XXX Kills some playback bits */
			APC_WRITE(sc, APC_CSR, APC_CSR_CAPTURE_PAUSE);
		} else {
			EBDMA_C_WRITE(sc, EBDMA_DCSR,
			    EBDMA_C_READ(sc, EBDMA_DCSR) & ~EBDCSR_DMAEN);
		}
		/* Waiting for capture FIFO to empty */
		status = cs4231_read(sc, CS_TEST_AND_INIT);
		for (i = CS_TIMEOUT;
		    i && (status & CAPTURE_OVERRUN) == 0; i--) {
			DELAY(5);
			status = cs4231_read(sc, CS_TEST_AND_INIT);
		}
		if (i == 0)
			device_printf(sc->sc_dev, "timeout waiting for "
			    "capture FIFO drain\n");
		cs4231_write(sc, CS_INTERFACE_CONFIG,
		    cs4231_read(sc, CS_INTERFACE_CONFIG) & (~CAPTURE_ENABLE));
	}
	ch->locked = 0;
	CS4231_UNLOCK(sc);
}
