Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 1 Jul 2004 18:32:25 +0900
From:      Pyun YongHyeon <yongari@kt-is.co.kr>
To:        sparc64@freebsd.org
Subject:   pcm sound driver for SBus Ultra1/Ultra2
Message-ID:  <20040701093225.GA12517@kt-is.co.kr>

next in thread | raw e-mail | index | archive | help

--/9DWx/yDrRhgMJTb
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

Hello All,

I don't know how may users want to hear sound on Ultra1/Ultra2.
But I wanted to hear some music while 'build world' is in progress.

<Driver summary>
 1. The driver is based on OpenBSD's driver and ISA mss driver
    on FreeBSD.
 2. Supports onboard CS4231A chip on SBus Ultra1/Ultra2.
 3. Capture(recodring) was not tested at all.
      - I don't have microphone.
 4. Due to lack of programming information for APC DMA, I
    used existing interfaces of OpenBSD driver. So it may have
    some bugs on FreeBSD.
 5. Full-duplex mode doesn't work.
 6. You may notice some noise when you work on ofw console. I don't
    know what is the reason, atm.
 7. Due to lack of X supports, I could't test with xmms or GUI based
    audio tools.
 8. If you have PCI/EBus based sparcs, they are not supported. Yes, they
    use the same chip but have different DMA interface. In addition, I
    don't have PCI/EBus based sparcs.

Installation
 1. get the driver patch file and fetch your system
 2. build kernel and kernel modules
 3. load snd_cs4231.ko and play
     Note, you may want to mute speaker output with mixer(8).

The attached patch is for -CURRENT, and is also available at:
 http://www.kr.freebsd.org/~yongari/cs4231.freebsd.diff

Corrections, suggestions welcome.
Thanks.

Regards,
Pyun YongHyeon
-- 
Pyun YongHyeon <http://www.kr.freebsd.org/~yongari>;

--/9DWx/yDrRhgMJTb
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="cs4231.freebsd.diff"

--- sys/dev/sound/isa/sndbuf_dma.c.orig	Mon Sep  8 01:28:02 2003
+++ sys/dev/sound/isa/sndbuf_dma.c	Tue Jun 29 12:47:14 2004
@@ -30,6 +30,44 @@
 
 SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/isa/sndbuf_dma.c,v 1.2 2003/09/07 16:28:02 cg Exp $");
 
+#ifdef __sparc64__
+/*
+ * XXX
+ * Sparc64 don't have ISA bus. Temp. glue code to load pcm module.
+ * pcm(4) should be architecture independent.
+ */
+int
+sndbuf_dmasetup(struct snd_dbuf *b, struct resource *drq)
+{
+	return (0);
+}
+
+int
+sndbuf_dmasetdir(struct snd_dbuf *b, int dir)
+{
+	return (0);
+}
+
+void
+sndbuf_dma(struct snd_dbuf *b, int go)
+{
+	return;
+}
+
+int
+sndbuf_dmaptr(struct snd_dbuf *b)
+{
+	return (0);
+}
+
+void
+sndbuf_dmabounce(struct snd_dbuf *b)
+{
+	return;
+}
+
+#else
+
 int
 sndbuf_dmasetup(struct snd_dbuf *b, struct resource *drq)
 {
@@ -101,3 +139,4 @@
 
 	/* tell isa_dma to bounce data in/out */
 }
+#endif
--- /dev/null	Thu Jul  1 18:00:00 2004
+++ sys/dev/sound/sbus/apcdmareg.h	Wed Jun 30 18:26:26 2004
@@ -0,0 +1,113 @@
+/*	$OpenBSD: apcdmareg.h,v 1.2 2003/06/02 18:53:18 jason Exp $	*/
+
+/*
+ * Copyright (c) 2001 Jason L. Wright (jason@thought.net)
+ * 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.
+ */
+
+/*
+ * Definitions for Sun APC DMA controller.
+ */
+
+/* APC DMA registers */
+#define	APC_CSR		0x0010		/* control/status */
+#define	APC_CVA		0x0020		/* capture virtual address */
+#define	APC_CC		0x0024		/* capture count */
+#define	APC_CNVA	0x0028		/* capture next virtual address */
+#define	APC_CNC		0x002c		/* capture next count */
+#define	APC_PVA		0x0030		/* playback virtual address */
+#define	APC_PC		0x0034		/* playback count */
+#define	APC_PNVA	0x0038		/* playback next virtual address */
+#define	APC_PNC		0x003c		/* playback next count */
+
+/*
+ * APC DMA Register definitions
+ */
+#define	APC_CSR_RESET		0x00000001	/* reset */
+#define	APC_CSR_CDMA_GO		0x00000004	/* capture dma go */
+#define	APC_CSR_PDMA_GO		0x00000008	/* playback dma go */
+#define	APC_CSR_CODEC_RESET	0x00000020	/* codec reset */
+#define	APC_CSR_CPAUSE		0x00000040	/* capture dma pause */
+#define	APC_CSR_PPAUSE		0x00000080	/* playback dma pause */
+#define	APC_CSR_CMIE		0x00000100	/* capture pipe empty enb */
+#define	APC_CSR_CMI		0x00000200	/* capture pipe empty intr */
+#define	APC_CSR_CD		0x00000400	/* capture nva dirty */
+#define	APC_CSR_CM		0x00000800	/* capture data lost */
+#define	APC_CSR_PMIE		0x00001000	/* pb pipe empty intr enable */
+#define	APC_CSR_PD		0x00002000	/* pb nva dirty */
+#define	APC_CSR_PM		0x00004000	/* pb pipe empty */
+#define	APC_CSR_PMI		0x00008000	/* pb pipe empty interrupt */
+#define	APC_CSR_EIE		0x00010000	/* error interrupt enable */
+#define	APC_CSR_CIE		0x00020000	/* capture intr enable */
+#define	APC_CSR_PIE		0x00040000	/* playback intr enable */
+#define	APC_CSR_GIE		0x00080000	/* general intr enable */
+#define	APC_CSR_EI		0x00100000	/* error interrupt */
+#define	APC_CSR_CI		0x00200000	/* capture interrupt */
+#define	APC_CSR_PI		0x00400000	/* playback interrupt */
+#define	APC_CSR_GI		0x00800000	/* general interrupt */
+
+#define	APC_CSR_PLAY			( \
+		APC_CSR_EI		| \
+	 	APC_CSR_GIE		| \
+		APC_CSR_PIE		| \
+		APC_CSR_EIE		| \
+		APC_CSR_PDMA_GO		| \
+		APC_CSR_PMIE		)
+
+#define	APC_CSR_CAPTURE			( \
+		APC_CSR_EI		| \
+	 	APC_CSR_GIE		| \
+		APC_CSR_CIE		| \
+		APC_CSR_EIE		| \
+		APC_CSR_CDMA_GO	)
+
+#define	APC_CSR_PLAY_PAUSE		(~( \
+		APC_CSR_PPAUSE		| \
+		APC_CSR_GI		| \
+		APC_CSR_PI		| \
+		APC_CSR_CI		| \
+		APC_CSR_EI		| \
+		APC_CSR_PMI		| \
+		APC_CSR_PMIE		| \
+		APC_CSR_CMI		| \
+		APC_CSR_CMIE		) )
+
+#define	APC_CSR_CAPTURE_PAUSE		(~( \
+		APC_CSR_PPAUSE		| \
+		APC_CSR_GI		| \
+		APC_CSR_PI		| \
+		APC_CSR_CI		| \
+		APC_CSR_EI		| \
+		APC_CSR_PMI		| \
+		APC_CSR_PMIE		| \
+		APC_CSR_CMI		| \
+		APC_CSR_CMIE		) )
+
+#define	APC_CSR_INTR_MASK		( \
+		APC_CSR_GI		| \
+		APC_CSR_PI		| \
+		APC_CSR_CI		| \
+		APC_CSR_EI		| \
+		APC_CSR_PMI		| \
+		APC_CSR_CMI		)
--- /dev/null	Thu Jul  1 18:00:00 2004
+++ sys/dev/sound/sbus/cs4231.c	Thu Jul  1 18:02:22 2004
@@ -0,0 +1,1030 @@
+/*
+ * Copyright (c) 1999 Jason L. Wright (jason@thought.net)
+ * Copyright (c) 2004 Pyun YongHyeon <yongari@kt-is.co.kr>
+ * 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/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/resource.h>
+
+#include <machine/bus.h>
+#include <dev/ofw/openfirm.h>
+#include <machine/ofw_machdep.h>
+
+#include <dev/sound/pcm/sound.h>
+#include <dev/sound/isa/mss.h>
+#include <dev/sound/sbus/apcdmareg.h>
+#include <dev/sound/sbus/cs4231.h>
+
+#include <sparc64/sbus/sbusvar.h>
+
+#include "mixer_if.h"
+
+/*
+ * This driver is for Sbus based sparcs(Ultra1 and Ultra2). If you have
+ * PCI/EBus based sparcs it is not supported by this driver.
+ * (We need another driver for those systems. Yes, they use the same chip
+ * cs4231A, but has different DMA engine and I don't have PCI/EBus based
+ * sparcs.
+ * Mixer routines were copied from ISA mss driver and modified.
+ *
+ * Though, cs4231 says it supports full-duplex mode, I doubt it due to
+ * lack of independent sampling frequency register. In addition, I can't
+ * find any documentation of Sun APC DMA programming information. I guessed
+ * the usage of APC DMA from existing OpenBSD's driver.
+ *
+ * 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	(8*1024)
+#define CS4231_DEFAULT_BUF_SZ	(32*1024)
+#define CS4231_MAX_BUF_SZ	(64*1024)
+/* It seems that 4KB is the best value. */
+#define CS4231_MAX_DMA_SZ	(4*1024)
+
+
+#undef CS4231_DEBUG
+#ifdef CS4231_DEBUG
+#define DPRINTF(x)		printf x
+#else
+#define DPRINTF(x)
+#endif
+
+struct cs4231_softc;
+
+struct cs4231_channel {
+	struct cs4231_softc	*parent;
+	struct pcm_channel	*channel;
+	struct snd_dbuf		*buffer;
+	int			dir;
+	int			locked;
+};
+
+struct cs4231_softc {
+	struct device		*sc_dev;
+	int			sc_rid;
+	struct resource		*sc_res;
+	bus_space_handle_t	sc_regh;
+	bus_space_tag_t		sc_regt;
+
+	int			sc_irqrid;
+	struct resource		*sc_irqres;
+	void			*sc_ih;
+	bus_dma_tag_t		sc_dmat;
+	u_int32_t		sc_bufsz;
+	struct cs4231_channel	sc_pch;
+	struct cs4231_channel	sc_rch;
+	int			sc_enabled;
+	struct mtx		*sc_lock;
+};
+
+static int	cs4231_sbus_probe(device_t);
+static int	cs4231_sbus_attach(device_t);
+static int	cs4231_sbus_detach(device_t);
+static int	cs4231_sbus_suspend(device_t);
+static int	cs4231_sbus_resume(device_t);
+static void	cs4231_free_resource(struct cs4231_softc *);
+static void	cs4231_power_reset(struct cs4231_softc *);
+static int	cs4231_enable(struct cs4231_softc *);
+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_intr(void *);
+static int	cs4231_mixer_init(struct snd_mixer *);
+static void	change_bits(mixer_tab *, u_int8_t *, u_int32_t, u_int32_t,
+    u_int32_t);
+static int	cs4231_mixer_set(struct snd_mixer *, u_int32_t, u_int32_t,
+    u_int32_t);
+static int	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 int	cs4231_chan_setspeed(kobj_t, void *, u_int32_t);
+static void	cs4231_chan_fs(struct cs4231_softc *, u_int8_t);
+static int	cs4231_chan_setblocksize(kobj_t, void *, u_int32_t);
+static int	cs4231_chan_trigger(kobj_t, void *, int);
+static int	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_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, (sc)->sc_regh, (r) << 2, (v))
+#define CS_READ(sc,r)		\
+    bus_space_read_1((sc)->sc_regt, (sc)->sc_regh, (r) << 2)
+
+#define APC_WRITE(sc,r,v)	\
+    bus_space_write_4(sc->sc_regt, sc->sc_regh, r, v)
+#define APC_READ(sc,r)		\
+    bus_space_read_4(sc->sc_regt, sc->sc_regh, r)
+
+static device_method_t cs4231_sbus_methods[] = {
+	DEVMETHOD(device_probe,		cs4231_sbus_probe),
+	DEVMETHOD(device_attach,	cs4231_sbus_attach),
+	DEVMETHOD(device_detach,	cs4231_sbus_detach),
+	DEVMETHOD(device_suspend,	cs4231_sbus_suspend),
+	DEVMETHOD(device_resume,	cs4231_sbus_resume),
+	{0, 0}
+};
+
+static driver_t cs4231_sbus_driver = {
+	"pcm",
+	cs4231_sbus_methods,
+	PCM_SOFTC_SIZE
+};
+
+DRIVER_MODULE(snd_cs4231, sbus, cs4231_sbus_driver, pcm_devclass, 0, 0);
+MODULE_DEPEND(snd_cs4231, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER);
+MODULE_VERSION(snd_cs4231, 1);
+
+
+static u_int32_t cs4231_fmt[] = {
+	AFMT_U8,
+	AFMT_STEREO | AFMT_U8,
+	AFMT_S16_LE,
+	AFMT_STEREO | AFMT_S16_LE,
+	AFMT_MU_LAW,
+	AFMT_STEREO | AFMT_MU_LAW,
+	AFMT_A_LAW,
+	AFMT_STEREO | AFMT_A_LAW,
+	0
+};
+
+static struct pcmchan_caps cs4231_caps = {5510, 48000, cs4231_fmt, 0};
+
+/*
+ * pcm(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),
+	{ 0, 0 }
+};
+CHANNEL_DECLARE(cs4231_chan); 
+
+/*
+ * pcm(4) mixer interface
+ */
+static mixer_ent cs4231_mix_devices[32][2] = {
+	MIX_NONE(SOUND_MIXER_VOLUME),
+	MIX_NONE(SOUND_MIXER_BASS),
+	MIX_NONE(SOUND_MIXER_TREBLE),
+	/* AUX1 */
+	MIX_ENT(SOUND_MIXER_SYNTH,	 2, 1, 0, 5,	 3, 1, 0, 5),
+	MIX_ENT(SOUND_MIXER_PCM,	 6, 1, 0, 6,	 7, 1, 0, 6),
+	MIX_ENT(SOUND_MIXER_SPEAKER,	26, 1, 0, 4,	 0, 0, 0, 0),
+	MIX_ENT(SOUND_MIXER_LINE,	18, 1, 0, 5,	19, 1, 0, 5),
+	MIX_ENT(SOUND_MIXER_MIC,	 0, 0, 5, 1,	 1, 0, 5, 1),
+	/*
+	 * XXX
+	 * AUX2 : Ultra1/Ultra2 has no internal CD-ROM audio in 
+	 */
+	MIX_ENT(SOUND_MIXER_CD,		 4, 1, 0, 5,	 5, 1, 0, 5),
+	MIX_ENT(SOUND_MIXER_IMIX,	13, 1, 2, 6,	 0, 0, 0, 0),
+	MIX_NONE(SOUND_MIXER_ALTPCM),
+	MIX_NONE(SOUND_MIXER_RECLEV),
+	MIX_ENT(SOUND_MIXER_IGAIN,	 0, 0, 0, 4,	 1, 0, 0, 4),
+	MIX_NONE(SOUND_MIXER_OGAIN),
+	MIX_NONE(SOUND_MIXER_LINE1),
+	MIX_NONE(SOUND_MIXER_LINE2),
+	MIX_NONE(SOUND_MIXER_LINE3),
+};
+
+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),
+	{ 0, 0 }
+};
+MIXER_DECLARE(cs4231_mixer);
+
+static int
+cs4231_sbus_probe(device_t dev)
+{
+	char *name;
+
+	name = sbus_get_name(dev);
+	if (strcmp("SUNW,CS4231", name) == 0) {
+		device_set_desc(dev, "Sun Audiocs CS4231A");
+		return (0);
+	}
+
+	return (ENXIO);
+}
+
+static int
+cs4231_sbus_attach(device_t dev)
+{
+	struct snddev_info *d;
+	struct cs4231_softc *sc;
+	char status[SND_STATUSLEN];
+
+	d = device_get_softc(dev);
+	sc = malloc(sizeof(struct cs4231_softc), M_DEVBUF, M_NOWAIT | M_ZERO);
+	if (sc == NULL) {
+		device_printf(dev, "cannot allocate softc\n");
+	}
+	sc->sc_dev = dev;
+	sc->sc_lock = snd_mtxcreate(device_get_nameunit(sc->sc_dev),
+	    "sound softc");
+
+	sc->sc_rid = 0;
+	if ((sc->sc_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
+	    &sc->sc_rid, RF_ACTIVE)) == NULL) {
+		device_printf(dev, "cannot map registers\n");
+                return (ENXIO);
+	}
+	sc->sc_regt = rman_get_bustag(sc->sc_res);
+	sc->sc_regh = rman_get_bushandle(sc->sc_res);
+	sc->sc_irqrid = 0;
+	if ((sc->sc_irqres = bus_alloc_resource_any(sc->sc_dev, SYS_RES_IRQ,
+	    &sc->sc_irqrid, RF_SHAREABLE | RF_ACTIVE)) == NULL) {
+		device_printf(sc->sc_dev, "cannot allocate interrupt\n");
+		goto fail;
+        }
+	if (snd_setup_intr(sc->sc_dev, sc->sc_irqres,
+	    INTR_MPSAFE, cs4231_intr, sc, &sc->sc_ih)) {
+		device_printf(sc->sc_dev, "cannot set up interrupt\n");
+		goto fail;
+        }
+
+	sc->sc_bufsz = pcm_getbuffersize(sc->sc_dev, CS4231_MIN_BUF_SZ,
+	    CS4231_DEFAULT_BUF_SZ, CS4231_MAX_BUF_SZ);
+	if (bus_dma_tag_create(
+			NULL,			/* parent */
+			64*1024, 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)) {
+		device_printf(sc->sc_dev, "cannot allocate parent DMA tag\n");
+		goto fail;
+	}
+	cs4231_enable(sc);
+	mixer_init(sc->sc_dev, &cs4231_mixer_class, sc);
+	if (pcm_register(sc->sc_dev, sc, 1, 1)) {
+		device_printf(sc->sc_dev, "cannot register to pcm\n");
+		goto fail;
+	}
+	pcm_addchan(sc->sc_dev, PCMDIR_REC, &cs4231_chan_class, sc);
+	pcm_addchan(sc->sc_dev, PCMDIR_PLAY, &cs4231_chan_class, sc);
+	snprintf(status, SND_STATUSLEN, "at mem 0x%lx irq %ld bufsz %u",
+	    rman_get_start(sc->sc_res), rman_get_start(sc->sc_irqres),
+	    sc->sc_bufsz);
+	pcm_setstatus(sc->sc_dev, status);
+
+	return (0);
+
+fail:
+	cs4231_free_resource(sc);
+	return (ENXIO);
+}
+
+static int
+cs4231_sbus_detach(device_t dev)
+{
+	struct cs4231_softc *sc;
+	int error;
+
+	error = pcm_unregister(dev);
+	if (error)
+		return (error);
+
+	sc = pcm_getdevinfo(dev);
+	cs4231_free_resource(sc);
+
+	return (0);
+}
+
+static int
+cs4231_sbus_suspend(device_t dev)
+{
+
+	return (ENXIO);
+}
+
+static int
+cs4231_sbus_resume(device_t dev)
+{
+
+	return (ENXIO);
+}
+
+static void
+cs4231_power_reset(struct cs4231_softc *sc)
+{
+
+	int i;
+
+	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));
+
+	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);
+        cs4231_write(sc, CS_PIN_CONTROL,
+            cs4231_read(sc, CS_PIN_CONTROL) | INTERRUPT_ENABLE);
+}
+
+static int
+cs4231_enable(struct cs4231_softc *sc)
+{
+	cs4231_power_reset(sc);
+	sc->sc_enabled = 1;
+        return (0);
+
+}
+
+static void
+cs4231_disable(struct cs4231_softc *sc)
+{
+	u_int8_t v;
+
+	if (sc->sc_enabled == 0)
+		return;
+
+	cs4231_halt(&sc->sc_pch);
+	cs4231_halt(&sc->sc_rch);
+	v = cs4231_read(sc, CS_PIN_CONTROL) & ~INTERRUPT_ENABLE;
+	cs4231_write(sc, CS_PIN_CONTROL, v);
+
+	/* reset APC */
+	APC_WRITE(sc, APC_CSR, APC_CSR_RESET);
+	DELAY(10);
+	APC_WRITE(sc, APC_CSR, 0);
+	DELAY(10);
+	sc->sc_enabled = 0;
+}
+
+static void
+cs4231_free_resource(struct cs4231_softc *sc)
+{
+	cs4231_disable(sc);
+	if (sc->sc_irqres) {
+		if (sc->sc_ih) {
+			bus_teardown_intr(sc->sc_dev, sc->sc_irqres,
+			    sc->sc_ih);
+			sc->sc_ih = NULL;
+		}
+		bus_release_resource(sc->sc_dev, SYS_RES_IRQ, sc->sc_irqrid,
+		    sc->sc_irqres);
+		sc->sc_irqres = NULL;
+	}
+	if (sc->sc_dmat)
+		bus_dma_tag_destroy(sc->sc_dmat);
+	if (sc->sc_res)
+		bus_release_resource(sc->sc_dev, SYS_RES_MEMORY, sc->sc_rid,
+		    sc->sc_res);
+	if (sc->sc_lock)
+		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_intr(void *arg)
+{
+	struct cs4231_softc *sc;
+	struct cs4231_channel *ch;
+	u_int32_t csr;
+	u_int8_t reg, 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);
+	}
+
+	if ((csr & APC_CSR_GIE) && (csr & APC_CSR_GI)) {
+		/* general interrupt */
+		status = CS_READ(sc, CS4231_STATUS);
+		if (status & (INTERRUPT_STATUS | SAMPLE_ERROR)) {
+			reg = cs4231_read(sc, CS_IRQ_STATUS);
+			if (reg & CS_AFS_PI) {
+				cs4231_write(sc, CS_LOWER_BASE_COUNT, 0xff);
+				cs4231_write(sc, CS_UPPER_BASE_COUNT, 0xff);
+			}
+			if (reg & CS_AFS_CI) {
+				cs4231_write(sc, CS_LOWER_REC_CNT, 0xff);
+				cs4231_write(sc, CS_UPPER_REC_CNT, 0xff);
+			}
+			CS_WRITE(sc, CS4231_STATUS, 0);
+		}
+	}
+
+	if ((csr & APC_CSR_PMIE) && (csr & APC_CSR_PMI)) {
+		u_long nextaddr, saddr;
+		u_int32_t togo;
+
+		ch = &sc->sc_pch;
+
+		CS4231_UNLOCK(sc);
+		chn_intr(ch->channel);
+		CS4231_LOCK(sc);
+
+		togo = sndbuf_getblksz(ch->buffer);
+		saddr = sndbuf_getbufaddr(ch->buffer);
+		nextaddr = APC_READ(sc, APC_PNVA) + togo;
+		if (nextaddr >=  saddr + sc->sc_bufsz)
+			nextaddr = saddr;
+		APC_WRITE(sc, APC_PNVA, nextaddr);
+		APC_WRITE(sc, APC_PNC, togo);
+	}
+
+	if ((csr & APC_CSR_CIE) && (csr & APC_CSR_CI) && (csr & APC_CSR_CD)) {
+		u_long nextaddr, saddr;
+		u_int32_t togo;
+
+		ch = &sc->sc_rch;
+
+		CS4231_UNLOCK(sc);
+		chn_intr(ch->channel);
+		CS4231_LOCK(sc);
+
+		togo = sndbuf_getblksz(ch->buffer);
+		saddr = sndbuf_getbufaddr(ch->buffer);
+		nextaddr = APC_READ(sc, APC_CNVA) + togo;
+		if (nextaddr >= saddr + sc->sc_bufsz)
+			nextaddr = saddr; 
+		APC_WRITE(sc, APC_CNVA, nextaddr);
+		APC_WRITE(sc, APC_CNC, togo);
+	}
+	CS4231_UNLOCK(sc);
+}
+
+static int
+cs4231_mixer_init(struct snd_mixer *m)
+{
+	mix_setdevs(m, MODE2_MIXER_DEVICES);
+	mix_setrecdevs(m, MSS_REC_DEVICES);
+
+	return (0);
+}
+
+/*
+ * copied from sys/dev/sound/isa/mss.c
+ */
+static void
+change_bits(mixer_tab *t, u_int8_t *regval, u_int32_t dev, u_int32_t chn,
+    u_int32_t newval)
+{
+	u_int8_t mask;
+	u_int32_t shift;
+
+	if ((*t)[dev][chn].polarity == 1)      /* reverse */
+		newval = 100 - newval ;
+
+	mask = (1 << (*t)[dev][chn].nbits) - 1;
+	newval = ((newval * mask) + 50) / 100; /* Scale it */
+	shift = (*t)[dev][chn].bitoffs /*- (*t)[dev][LEFT_CHN].nbits + 1*/;
+
+	*regval &= ~(mask << shift);	/* Filter out the previous value */
+	*regval |= (newval & mask) << shift;	/* Set the new value */
+}
+
+/*
+ * copied from sys/dev/sound/isa/mss.c
+ */
+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;
+	mixer_tab *mix_d;
+	u_int8_t old, val, reg;
+
+	sc = mix_getdevinfo(m);
+	CS4231_LOCK(sc);
+	mix_d = &cs4231_mix_devices;
+	if ((*mix_d)[dev][LEFT_CHN].nbits == 0) {
+		DEB(printf("nbits = 0 for dev %d\n", dev));
+                return -1;
+        }
+
+	if ((*mix_d)[dev][RIGHT_CHN].nbits == 0)
+		right = left; /* mono */
+
+	/* Set the left channel */
+	reg = (*mix_d)[dev][LEFT_CHN].regno;
+	old = val = cs4231_read(sc, reg);
+	/* if volume is 0, mute chan. Otherwise, unmute. */
+	if (reg != 0) {
+		if (reg == CS_MONO_IO_CONTROL)
+			val = (left == 0) ? old | MONO_OUTPUT_MUTE : old & 0xef;
+		else if (reg == CS_DIGITAL_MIX)
+			val = (left == 0) ? 0 : old & 0xfd;
+		else
+			val = (left == 0) ? old | 0x80 : old & 0x7f;
+	}
+	change_bits(mix_d, &val, dev, LEFT_CHN, left);
+	cs4231_write(sc, reg, val);
+
+	if ((*mix_d)[dev][RIGHT_CHN].nbits != 0) { /* have stereo */
+		/* Set the right channel */
+		reg = (*mix_d)[dev][RIGHT_CHN].regno;
+		old = val = cs4231_read(sc, reg);
+		if (reg != 1)
+			val = (right == 0) ? old | 0x80 : old & 0x7f;
+		change_bits(mix_d, &val, dev, RIGHT_CHN, right);
+		cs4231_write(sc, reg, val);
+	}
+	CS4231_UNLOCK(sc);
+
+	return (left | (right << 8));
+}
+
+static int
+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:
+	case SOUND_MASK_LINE3:
+		v = CS_IN_LINE;
+		break;
+
+	case SOUND_MASK_CD:
+	case SOUND_MASK_LINE1:
+		v = CS_IN_AUX1;
+		break;
+
+	case SOUND_MASK_IMIX:
+		v = CS_IN_DAC;
+		break;
+
+	case SOUND_MASK_MIC:
+	default:
+		v = CS_IN_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;
+
+	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 (sndbuf_alloc(ch->buffer, sc->sc_dmat, sc->sc_bufsz) != 0)
+		return (NULL);
+	DPRINTF(("%s channel addr: %lx\n", dir == PCMDIR_PLAY ? "PLAY" : "REC",
+	    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;
+
+	encoding = format & ~(AFMT_STEREO | AFMT_FULLDUPLEX);
+	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_U16_BE:
+		fs = CS_AFMT_U16_BE;
+		break;
+	default:
+		fs = CS_AFMT_U8;
+		break;
+	}
+
+	if (format & AFMT_STEREO)
+		fs |= CS_AFMT_STEREO;
+	
+	DPRINTF(("FORMAT: %s : 0x%x\n", ch->dir == PCMDIR_PLAY ? "playback" :
+	    "capture", format));
+	CS4231_LOCK(sc);
+	v = cs4231_read(sc, CS_CLOCK_DATA_FORMAT);
+	v &= CS_CLOCK_DATA_FORMAT_MASK;
+	fs |= v;
+	cs4231_chan_fs(sc, fs);
+	CS4231_UNLOCK(sc);
+	return (0);
+}
+
+static int
+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;
+	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;
+
+	CS4231_LOCK(sc);
+	fs = cs4231_read(sc, CS_CLOCK_DATA_FORMAT);
+	fs &= ~CS_CLOCK_DATA_FORMAT_MASK;
+	fs |= speed_table[sel].bits;
+	cs4231_chan_fs(sc, fs);
+	CS4231_UNLOCK(sc);
+	return (speed);
+}
+
+static void
+cs4231_chan_fs(struct cs4231_softc *sc, u_int8_t fs)
+{
+	int i, doreset;
+	u_int8_t v;
+
+	CS4231_LOCK_ASSERT(sc);
+
+	/* set autocalibration */
+	doreset = 0;
+	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);
+
+	/* playback channel */
+	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 sperate fs setup for playback/capture.
+	 * I believe this will break full-duplex operation.
+	 */
+	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++;
+	}
+	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++;
+	}
+	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_power_reset(sc);
+		CS4231_UNLOCK(sc); /* XXX */
+		if (mixer_reinit(sc->sc_dev) == -1) 
+			device_printf(sc->sc_dev,
+			    "unable to reinitialize the mixer\n");
+		CS4231_LOCK(sc);
+	}
+}
+
+static int
+cs4231_chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize)
+{
+	struct cs4231_softc *sc;
+	struct cs4231_channel *ch;
+	int nblks;
+
+	ch = data;
+	sc = ch->parent;
+
+	if (blocksize > CS4231_MAX_DMA_SZ)
+		blocksize = CS4231_MAX_DMA_SZ;
+	nblks = sc->sc_bufsz / blocksize;
+	sndbuf_resize(ch->buffer, nblks, blocksize);
+
+        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 int
+cs4231_chan_getptr(kobj_t obj, void *data)
+{
+	struct cs4231_softc *sc;
+	struct cs4231_channel *ch;
+	u_int32_t naddr;
+	int ptr, sz;
+
+	ch = data;
+	sc = ch->parent;
+
+	CS4231_LOCK(sc);
+	if (ch->dir == PCMDIR_PLAY)
+		naddr = APC_READ(sc, APC_PNVA);
+	else
+		naddr = APC_READ(sc, APC_CNVA);
+	sz = sndbuf_getsize(ch->buffer);
+	ptr = naddr + sndbuf_getblksz(ch->buffer) -
+	    sndbuf_getbufaddr(ch->buffer);
+	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;
+	u_int32_t csr, togo;
+	u_int32_t nextaddr, saddr;
+
+	sc = ch->parent;
+
+	CS4231_LOCK(sc);
+	if (ch->locked) {
+		device_printf(sc->sc_dev, "already triggered\n");
+		CS4231_UNLOCK(sc);
+		return;
+	}
+
+	nextaddr = sndbuf_getbufaddr(ch->buffer);
+	togo = sndbuf_getblksz(ch->buffer);
+	if (ch->dir == PCMDIR_PLAY) {
+		DPRINTF(("TRG: PNVA = %x, togo = %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_LOWER_BASE_COUNT, 0xff);
+			cs4231_write(sc, CS_UPPER_BASE_COUNT, 0xff);
+			cs4231_write(sc, CS_INTERFACE_CONFIG,
+			    cs4231_read(sc, CS_INTERFACE_CONFIG) |
+			    PLAYBACK_ENABLE);
+		}
+	} else {
+		DPRINTF(("TRG: CNVA = %x, togo = %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_LOWER_REC_CNT, 0xff);
+			cs4231_write(sc, CS_UPPER_REC_CNT, 0xff);
+			cs4231_write(sc, CS_INTERFACE_CONFIG,
+			    cs4231_read(sc, CS_INTERFACE_CONFIG) |
+			    CAPTURE_ENABLE);
+		}
+		/* try to update next samples */
+		if (APC_READ(sc, APC_CSR) & APC_CSR_CD) {
+			saddr = sndbuf_getbufaddr(ch->buffer);
+			nextaddr = APC_READ(sc, APC_CNVA) + togo;
+			if (nextaddr >= saddr + sc->sc_bufsz)
+				nextaddr = saddr;
+			APC_WRITE(sc, APC_CNVA, nextaddr);
+			APC_WRITE(sc, APC_CNC, togo);
+		}
+	}
+	ch->locked = 1;
+	CS4231_UNLOCK(sc);
+}
+
+static void
+cs4231_halt(struct cs4231_channel *ch)
+{
+	struct cs4231_softc *sc;
+
+	sc = ch->parent;
+	CS4231_LOCK(sc);
+
+	if (ch->dir == PCMDIR_PLAY ) {
+		/* 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));
+		cs4231_write(sc, CS_INTERFACE_CONFIG,
+		    cs4231_read(sc, CS_INTERFACE_CONFIG) & (~PLAYBACK_ENABLE));
+	} else {
+		/* XXX Kills some playback bits */
+		APC_WRITE(sc, APC_CSR, APC_CSR_CAPTURE_PAUSE);
+		cs4231_write(sc, CS_INTERFACE_CONFIG,
+		    cs4231_read(sc, CS_INTERFACE_CONFIG) & (~CAPTURE_ENABLE));
+	}
+	ch->locked = 0;
+	CS4231_UNLOCK(sc);
+}
--- /dev/null	Thu Jul  1 18:00:00 2004
+++ sys/dev/sound/sbus/cs4231.h	Wed Jun 30 18:26:20 2004
@@ -0,0 +1,228 @@
+/*-
+ * Copyright (c) 1996 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Ken Hornstein and John Kohl.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *        This product includes software developed by the NetBSD 
+ *	  Foundation, Inc. and its contributors.
+ * 4. Neither the name of The NetBSD Foundation nor the names of its 
+ *    contributors may be used to endorse or promote products derived 
+ *    from this software without specific prior written permission.
+ *
+ * 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 REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Register defs for Crystal Semiconductor CS4231 Audio Codec/mixer
+ * chip, used on Gravis UltraSound MAX cards.
+ *
+ * Block diagram:
+ *             +----------------------------------------------------+
+ *             |						    |
+ *             |   +----------------------------------------------+ |
+ *	       |   |mixed in       +-+  		          | |
+ *	       |   +------------>--| |  		          | |
+ *             | mic in            | |			          | |
+ *   Mic --+-->| --------- GAIN ->-| |			          | |
+ *         |   | AUX 1 in          |M|				  | |
+ *   GF1 --)-->| -------------+-->-|U|				  | |
+ *	   |   | Line in      |	   |X|---- GAIN ----------+	  | |
+ *  Line --)-->| ---------+---)-->-| |			  |	  | |
+ *	   |   |	  |   |    | |			  |	  | |
+ *	   |   |	  |   |    +-+		         ADC 	  | |
+ *	   |   |	  |   |      		          | 	  | |
+ *	   |   |	  |   |				  |	  | |
+ *	   |   |	  |   +--- L/M --\		  |	  | | AMP-->
+ *	   |   |	  |   	   	  \		  |	  | |  |
+ *	   |   |	  |   	   	   \	          |	  | |  |
+ *	   |   |	  +---- L/M -------O-->--+--------)-------+-|--+-> line
+ *	   |   |   mono in	       	  /|     |        |	    |
+ *	   +---|-->------------ L/M -----/ |     |        |	    |
+ *	       |   AUX 2 in		   |     |        |	    |
+ *  CD --------|-->------------ L/M -------+    L/M       |	    |
+ *	       |				 |        v	    |
+ *	       |				 |        |	    |
+ *	       |				DAC       |	    |
+ *	       |				 |        |	    |
+ *             +----------------------------------------------------+
+ *	       					 |        |
+ *						 |        |
+ *						 v        v
+ *     	       	       	       	       	       	  Pc BUS (DISK) ???
+ *
+ * Documentation for this chip can be found at:
+ *	http://www.cirrus.com/products/overviews/cs4231.html
+ */
+
+/*
+ * This file was merged from two header files.(ad1848reg.h and cs4231reg.h)
+ * And the suffix AD1848 and SP was changed to CS4231 and CS respectively.
+ */
+/* CS4231 direct registers */
+#define CS4231_IADDR		0x00
+#define CS4231_IDATA		0x01
+#define CS4231_STATUS		0x02
+#define CS4231_PIO		0x03
+
+/* Index address register */
+#define CS_IN_INIT		0x80
+#define MODE_CHANGE_ENABLE	0x40
+#define TRANSFER_DISABLE	0x20
+#define ADDRESS_MASK		0xe0
+
+/* Status bits */
+#define INTERRUPT_STATUS	0x01
+#define PLAYBACK_READY		0x02
+#define PLAYBACK_LEFT		0x04
+/* pbright is not left */
+#define PLAYBACK_UPPER		0x08
+/* bplower is not upper */
+#define SAMPLE_ERROR		0x10
+#define CAPTURE_READY		0x20
+#define CAPTURE_LEFT		0x40
+/* cpright is not left */
+#define CAPTURE_UPPER		0x80
+/* cplower is not upper */
+
+/* CS4231 indirect mapped registers */
+#define CS_LEFT_INPUT_CONTROL	0x00
+#define CS_RIGHT_INPUT_CONTROL	0x01
+#define CS_LEFT_AUX1_CONTROL	0x02
+#define CS_RIGHT_AUX1_CONTROL	0x03
+#define CS_LEFT_AUX2_CONTROL	0x04
+#define CS_RIGHT_AUX2_CONTROL	0x05
+#define CS_LEFT_OUTPUT_CONTROL	0x06
+#define CS_RIGHT_OUTPUT_CONTROL	0x07
+#define CS_CLOCK_DATA_FORMAT	0x08
+#define CS_INTERFACE_CONFIG	0x09
+#define CS_PIN_CONTROL		0x0a
+#define CS_TEST_AND_INIT	0x0b
+#define CS_MISC_INFO		0x0c
+#define CS_DIGITAL_MIX		0x0d
+#define CS_UPPER_BASE_COUNT	0x0e
+#define CS_LOWER_BASE_COUNT	0x0f
+/* CS4231/AD1845 mode2 registers; added to AD1848 registers */
+#define CS_ALT_FEATURE1		0x10
+#define CS_ALT_FEATURE2		0x11
+#define CS_LEFT_LINE_CONTROL	0x12
+#define CS_RIGHT_LINE_CONTROL	0x13
+#define CS_TIMER_LOW		0x14
+#define CS_TIMER_HIGH		0x15
+#define CS_UPPER_FREQUENCY_SEL	0x16
+#define CS_LOWER_FREQUENCY_SEL	0x17
+#define CS_IRQ_STATUS		0x18
+#define CS_VERSION_ID		0x19
+#define CS_MONO_IO_CONTROL	0x1a
+#define CS_POWERDOWN_CONTROL	0x1b
+#define CS_REC_FORMAT		0x1c
+#define CS_XTAL_SELECT		0x1d
+#define CS_UPPER_REC_CNT	0x1e
+#define CS_LOWER_REC_CNT	0x1f
+
+#define CS_IN_MASK		0x2f
+#define CS_IN_LINE		0x00
+#define CS_IN_AUX1		0x40
+#define CS_IN_MIC		0x80
+#define CS_IN_DAC		0xc0
+#define CS_MIC_GAIN_ENABLE	0x20
+#define CS_IN_GAIN_MASK		0xf0
+
+/* Aux input control - registers I2 (channel 1,left); I3 (channel 1,right)
+				 I4 (channel 2,left); I5 (channel 2,right) */
+#define AUX_INPUT_ATTEN_BITS	0x1f
+#define AUX_INPUT_ATTEN_MASK	0xe0
+#define AUX_INPUT_MUTE		0x80
+
+/* Output bits - registers I6,I7*/
+#define OUTPUT_MUTE		0x80
+#define OUTPUT_ATTEN_BITS	0x3f
+#define OUTPUT_ATTEN_MASK	(~OUTPUT_ATTEN_BITS & 0xff)
+
+/* Clock and Data format reg bits (some also Capture Data format) - reg I8 */
+#define CS_CLOCK_DATA_FORMAT_MASK 0x0f
+#define CLOCK_XTAL1		0x00
+#define CLOCK_XTAL2		0x01
+#define CLOCK_FREQ_MASK		0xf1
+#define CS_AFMT_STEREO		0x10
+#define CS_AFMT_U8		0x00
+#define CS_AFMT_MU_LAW		0x20
+#define CS_AFMT_S16_LE		0x40
+#define CS_AFMT_A_LAW		0x60
+#define CS_AFMT_IMA_ADPCM	0xa0
+#define CS_AFMT_U16_BE		0xc0
+
+/* Interface Configuration reg bits - register I9 */
+#define PLAYBACK_ENABLE		0x01
+#define CAPTURE_ENABLE		0x02
+#define DUAL_DMA		0x00
+#define SINGLE_DMA		0x04
+#define AUTO_CAL_ENABLE		0x08
+#define PLAYBACK_PIO_ENABLE	0x40
+#define CAPTURE_PIO_ENABLE	0x80
+
+/* Pin control bits - register I10 */
+#define INTERRUPT_ENABLE	0x02
+#define XCTL0_ENABLE		0x40
+#define XCTL1_ENABLE		0x80
+
+/* Test and init reg bits - register I11 (read-only) */
+#define OVERRANGE_LEFT_MASK	0xfc
+#define OVERRANGE_RIGHT_MASK	0xf3
+#define DATA_REQUEST_STATUS	0x10
+#define AUTO_CAL_IN_PROG	0x20
+#define PLAYBACK_UNDERRUN	0x40
+#define CAPTURE_OVERRUN		0x80
+
+/* Miscellaneous Control reg bits - register I12 */
+#define CS_ID_MASK		0x70
+#define CS_MODE2		0x40
+
+/* Digital Mix Control reg bits - register I13 */
+#define DIGITAL_MIX1_ENABLE	0x01
+#define MIX_ATTEN_MASK		0x03
+
+/* alternate feature status(I24) */
+#define CS_AFS_TI		0x40		/* timer interrupt */
+#define CS_AFS_CI		0x20		/* capture interrupt */
+#define CS_AFS_PI		0x10		/* playback interrupt */
+#define CS_AFS_CU		0x08		/* capture underrun */
+#define CS_AFS_CO		0x04		/* capture overrun */
+#define CS_AFS_PO		0x02		/* playback overrun */
+#define CS_AFS_PU		0x01		/* playback underrun */
+
+
+/* Miscellaneous Control reg bits */
+#define CS_MODE2		0x40
+
+#define MONO_INPUT_ATTEN_BITS	0x0f
+#define MONO_INPUT_ATTEN_MASK	0xf0
+#define MONO_OUTPUT_MUTE	0x40
+#define MONO_INPUT_MUTE		0x80
+#define MONO_INPUT_MUTE_MASK	0x7f
+
+#define LINE_INPUT_ATTEN_BITS	0x1f
+#define LINE_INPUT_ATTEN_MASK	0xe0
+#define LINE_INPUT_MUTE		0x80
+#define LINE_INPUT_MUTE_MASK	0x7f
--- sys/modules/Makefile.orig	Mon Jun 28 18:03:48 2004
+++ sys/modules/Makefile	Wed Jun 30 20:03:47 2004
@@ -417,6 +417,7 @@
 .if ${MACHINE_ARCH} == "sparc64"
 _gem=		gem
 _hme=		hme
+_sound=		sound
 .endif
 
 .if defined(MODULES_OVERRIDE) && !defined(ALL_MODULES)
--- sys/modules/sound/driver/Makefile.orig	Sun Aug 18 01:23:44 2002
+++ sys/modules/sound/driver/Makefile	Wed Jun 30 18:33:09 2004
@@ -1,6 +1,6 @@
 # $FreeBSD: src/sys/modules/sound/driver/Makefile,v 1.15 2002/08/17 16:23:44 orion Exp $
 
-SUBDIR  = als4000 ad1816 cmi cs4281 csa ds1 emu10k1 es137x ess
+SUBDIR  = als4000 ad1816 cmi cs4231 cs4281 csa ds1 emu10k1 es137x ess
 SUBDIR += fm801 ich maestro maestro3 mss neomagic sb16 sb8 sbc solo
 SUBDIR += t4dwave via8233 via82c686 vibes
 SUBDIR += driver uaudio
--- /dev/null	Thu Jul  1 18:00:00 2004
+++ sys/modules/sound/driver/cs4231/Makefile	Wed Jun 30 18:32:25 2004
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+.PATH: ${.CURDIR}/../../../../dev/sound/sbus
+
+KMOD=	snd_cs4231
+SRCS=	device_if.h bus_if.h
+SRCS+=	cs4231.c
+
+.include <bsd.kmod.mk>

--/9DWx/yDrRhgMJTb--



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20040701093225.GA12517>