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

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

--W/nzBZO5zC0uMSeA
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 summery
 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
    other 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 as usual 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>;

--W/nzBZO5zC0uMSeA
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>

--W/nzBZO5zC0uMSeA--



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