changeset 10553:0aa4533c70f5

PSARC 2009/487 audiosolo driver 6881873 support ESS Solo-1 audio
author Garrett D'Amore <Garrett.Damore@Sun.COM>
date Tue, 15 Sep 2009 21:03:09 -0700
parents bf9134a043f1
children 7b2fc4ef618c
files usr/src/pkgdefs/SUNWaudiosolo/Makefile usr/src/pkgdefs/SUNWaudiosolo/depend usr/src/pkgdefs/SUNWaudiosolo/pkginfo.tmpl usr/src/pkgdefs/SUNWaudiosolo/postinstall.tmpl usr/src/pkgdefs/SUNWaudiosolo/preremove.tmpl usr/src/pkgdefs/SUNWaudiosolo/prototype_com usr/src/pkgdefs/SUNWaudiosolo/prototype_i386 usr/src/uts/common/Makefile.files usr/src/uts/common/Makefile.rules usr/src/uts/common/io/audio/drv/audiosolo/audiosolo.c usr/src/uts/intel/Makefile.intel.shared usr/src/uts/intel/audiosolo/Makefile
diffstat 12 files changed, 1955 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/pkgdefs/SUNWaudiosolo/Makefile	Tue Sep 15 21:03:09 2009 -0700
@@ -0,0 +1,36 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+
+include ../Makefile.com
+
+TMPLFILES += postinstall preremove
+
+.KEEP_STATE:
+
+all: $(FILES) depend
+install: all pkg
+
+include ../Makefile.targ
+include ../Makefile.prtarg
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/pkgdefs/SUNWaudiosolo/depend	Tue Sep 15 21:03:09 2009 -0700
@@ -0,0 +1,50 @@
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# This package information file defines software dependencies associated
+# with the pkg.  You can define three types of pkg dependencies with this file:
+#	 P indicates a prerequisite for installation
+#	 I indicates an incompatible package
+#	 R indicates a reverse dependency
+# <pkg.abbr> see pkginfo(4), PKG parameter
+# <name> see pkginfo(4), NAME parameter
+# <version> see pkginfo(4), VERSION parameter
+# <arch> see pkginfo(4), ARCH parameter
+# <type> <pkg.abbr> <name>
+# 	(<arch>)<version>
+# 	(<arch>)<version>
+# 	...
+# <type> <pkg.abbr> <name>
+# ...
+#
+
+P SUNWcar	Core Architecture, (Root)
+P SUNWcakr	Core Solaris Kernel Architecture (Root)
+P SUNWkvm	Core Architecture, (Kvm)
+P SUNWcsr	Core Solaris, (Root)
+P SUNWckr	Core Solaris Kernel (Root)
+P SUNWcnetr	Core Solaris Network Infrastructure (Root)
+P SUNWcsu	Core Solaris, (Usr)
+P SUNWcsd	Core Solaris Devices
+P SUNWcsl	Core Solaris Libraries
+P SUNWaudd	Audio Drivers
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/pkgdefs/SUNWaudiosolo/pkginfo.tmpl	Tue Sep 15 21:03:09 2009 -0700
@@ -0,0 +1,57 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+
+#
+# This required package information file describes characteristics of the
+# package, such as package abbreviation, full package name, package version,
+# and package architecture.
+#
+PKG="SUNWaudiosolo"
+NAME="ESS Solo-1 Audio Driver"
+ARCH="ISA"
+VERSION="ONVERS,REV=0.0.0"
+CATEGORY="system"
+DESC="SunOS audio device driver for ESS Solo-1"
+SUNW_PRODNAME="SunOS"
+SUNW_PRODVERS="RELEASE/VERSION"
+SUNW_PKGTYPE="root"
+MAXINST="1000"
+VENDOR="Sun Microsystems, Inc."
+HOTLINE="Please contact your local service provider"
+EMAIL=""
+CLASSES="none preserve"
+BASEDIR=/
+SUNW_PKGVERS="1.0"
+SUNW_PKG_ALLZONES="true"
+SUNW_PKG_HOLLOW="true"
+SUNW_PKG_THISZONE="false"
+#VSTOCK="<reserved by Release Engineering for package part #>"
+#ISTATES="<developer defined>"
+#RSTATES='<developer defined>'
+#ULIMIT="<developer defined>"
+#ORDER="<developer defined>"
+#PSTAMP="<developer defined>"
+#INTONLY="<developer defined>"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/pkgdefs/SUNWaudiosolo/postinstall.tmpl	Tue Sep 15 21:03:09 2009 -0700
@@ -0,0 +1,34 @@
+#! /bin/sh
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+# SUNWaudiols postinstall script
+
+include drv_utils
+
+AUDIOSOLO_ALIASES="\
+	\"pci125d,1969\"	\
+	"
+
+pkg_drvadd -i "${AUDIOSOLO_ALIASES}" audiosolo || exit 1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/pkgdefs/SUNWaudiosolo/preremove.tmpl	Tue Sep 15 21:03:09 2009 -0700
@@ -0,0 +1,30 @@
+#! /bin/sh
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+# SUNWaudiosolo preremove script
+
+include drv_utils
+
+pkg_drvrem audiosolo || exit 1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/pkgdefs/SUNWaudiosolo/prototype_com	Tue Sep 15 21:03:09 2009 -0700
@@ -0,0 +1,47 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+# This required package information file contains a list of package contents.
+# The 'pkgmk' command uses this file to identify the contents of a package
+# and their location on the development machine when building the package.
+# Can be created via a text editor or through use of the 'pkgproto' command.
+
+#!search <pathname pathname ...>	# where to find pkg objects
+#!include <filename>			# include another 'prototype' file
+#!default <mode> <owner> <group>	# default used if not specified on entry
+#!<param>=<value>			# puts parameter in pkg environment
+
+# packaging files
+i pkginfo
+i copyright
+i depend
+i postinstall
+i preremove
+#
+# source locations relative to the prototype file
+#
+# SUNWaudiosolo
+#
+d none kernel 755 root sys
+d none kernel/drv 755 root sys
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/pkgdefs/SUNWaudiosolo/prototype_i386	Tue Sep 15 21:03:09 2009 -0700
@@ -0,0 +1,48 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+# This required package information file contains a list of package contents.
+# The 'pkgmk' command uses this file to identify the contents of a package
+# and their location on the development machine when building the package.
+# Can be created via a text editor or through use of the 'pkgproto' command.
+
+#!search <pathname pathname ...>	# where to find pkg objects
+#!include <filename>			# include another 'prototype' file
+#!default <mode> <owner> <group>	# default used if not specified on entry
+#!<param>=<value>			# puts parameter in pkg environment
+
+#
+# Include ISA independent files (prototype_com)
+#
+!include prototype_com
+#
+# List files which are i386 specific here
+#
+# source locations relative to the prototype file
+#
+# SUNWaudiols
+#
+f none kernel/drv/audiosolo 755 root sys
+d none kernel/drv/amd64 755 root sys
+f none kernel/drv/amd64/audiosolo 755 root sys
--- a/usr/src/uts/common/Makefile.files	Tue Sep 15 19:09:53 2009 -0700
+++ b/usr/src/uts/common/Makefile.files	Tue Sep 15 21:03:09 2009 -0700
@@ -446,6 +446,8 @@
 
 AUDIOPCI_OBJS += audiopci.o
 
+AUDIOSOLO_OBJS += audiosolo.o
+
 AUDIOTS_OBJS +=	audiots.o
 
 AC97_OBJS += ac97.o ac97_ad.o ac97_alc.o ac97_cmi.o
--- a/usr/src/uts/common/Makefile.rules	Tue Sep 15 19:09:53 2009 -0700
+++ b/usr/src/uts/common/Makefile.rules	Tue Sep 15 21:03:09 2009 -0700
@@ -592,6 +592,10 @@
 	$(COMPILE.c) -o $@ $<
 	$(CTFCONVERT_O)
 
+$(OBJS_DIR)/%.o:		$(UTSBASE)/common/io/audio/drv/audiosolo/%.c
+	$(COMPILE.c) -o $@ $<
+	$(CTFCONVERT_O)
+
 $(OBJS_DIR)/%.o:		$(UTSBASE)/common/io/audio/drv/audiots/%.c
 	$(COMPILE.c) -o $@ $<
 	$(CTFCONVERT_O)
@@ -1862,6 +1866,9 @@
 $(LINTS_DIR)/%.ln:		$(UTSBASE)/common/io/audio/drv/audiop16x/%.c
 	@($(LHEAD) $(LINT.c) $< $(LTAIL))
 
+$(LINTS_DIR)/%.ln:		$(UTSBASE)/common/io/audio/drv/audiosolo/%.c
+	@($(LHEAD) $(LINT.c) $< $(LTAIL))
+
 $(LINTS_DIR)/%.ln:		$(UTSBASE)/common/io/audio/drv/audiots/%.c
 	@($(LHEAD) $(LINT.c) $< $(LTAIL))
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/io/audio/drv/audiosolo/audiosolo.c	Tue Sep 15 21:03:09 2009 -0700
@@ -0,0 +1,1562 @@
+/*
+ * Copyright (c) 1999 Cameron Grant <cg@freebsd.org>
+ *
+ * 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 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 AUTHOR 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.
+ */
+
+/*
+ * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/*
+ * Copyright (C) 4Front Technologies 1996-2008.
+ */
+
+#include <sys/audio/audio_driver.h>
+#include <sys/note.h>
+#include <sys/pci.h>
+#include <sys/stdbool.h>
+
+
+/*
+ * NB: The Solo-1 is a bit schizophrenic compared to most devices.
+ * It has two separate DMA engines for PCM data.  The first can do
+ * either capture or playback, and supports various Sound Blaster
+ * compatibility features.  The second is dedicated to playback.  The
+ * two engines have very little in common when it comes to programming
+ * them.
+ *
+ * We configure engine 1 for record, and engine 2 for playback.  Both
+ * are configured for 48 kHz stereo 16-bit signed PCM.
+ */
+
+/*
+ * ESS Solo-1 only implements the low 24-bits on Audio1, and requires
+ * 64KB alignment.  For Audio2, it implements the full 32-bit address
+ * space, but requires a 1MB address boundary.  Audio1 is used for
+ * recording, and Audio2 is used for playback.
+ */
+static struct ddi_dma_attr dma_attr_audio1 = {
+	DMA_ATTR_VERSION,	/* dma_attr_version */
+	0x0,			/* dma_attr_addr_lo */
+	0x00ffffffU,		/* dma_attr_addr_hi */
+	0xffff,			/* dma_attr_count_max */
+	0x10000,		/* dma_attr_align */
+	0x7f,			/* dma_attr_burstsizes */
+	0x4,			/* dma_attr_minxfer */
+	0xffff,			/* dma_attr_maxxfer */
+	0xffff,			/* dma_attr_seg */
+	0x1,			/* dma_attr_sgllen */
+	0x1,			/* dma_attr_granular */
+	0			/* dma_attr_flags */
+};
+
+static struct ddi_dma_attr dma_attr_audio2 = {
+	DMA_ATTR_VERSION,	/* dma_attr_version */
+	0x0,			/* dma_attr_addr_lo */
+	0xffffffffU,		/* dma_attr_addr_hi */
+	0xfff0,			/* dma_attr_count_max */
+	0x100000,		/* dma_attr_align */
+	0x7f,			/* dma_attr_burstsizes */
+	0x4,			/* dma_attr_minxfer */
+	0xfff0,			/* dma_attr_maxxfer */
+	0xffff,			/* dma_attr_seg */
+	0x1,			/* dma_attr_sgllen */
+	0x1,			/* dma_attr_granular */
+	0			/* dma_attr_flags */
+};
+
+static ddi_device_acc_attr_t acc_attr = {
+	DDI_DEVICE_ATTR_V0,
+	DDI_STRUCTURE_LE_ACC,
+	DDI_STRICTORDER_ACC
+};
+
+static ddi_device_acc_attr_t buf_attr = {
+	DDI_DEVICE_ATTR_V0,
+	DDI_NEVERSWAP_ACC,
+	DDI_STRICTORDER_ACC
+};
+
+
+/*
+ * For the sake of simplicity, this driver fixes a few parameters with
+ * constants.  If you want these values to be tunable, upgrade to a
+ * nicer and newer device.  This is all tuned for 100 Hz (10
+ * millisecs) latency.
+ */
+#define	SOLO_RATE	48000
+#define	SOLO_INTRS	100
+#define	SOLO_FRAGFR	(SOLO_RATE / SOLO_INTRS)
+#define	SOLO_NFRAGS	8
+#define	SOLO_NCHAN	2
+#define	SOLO_SAMPSZ	2
+#define	SOLO_FRAGSZ	(SOLO_FRAGFR * (SOLO_NCHAN * SOLO_SAMPSZ))
+#define	SOLO_BUFFR	(SOLO_NFRAGS * SOLO_FRAGFR)
+#define	SOLO_BUFSZ	(SOLO_NFRAGS * SOLO_FRAGSZ)
+
+#define	INPUT_MIC	0
+#define	INPUT_LINE	1
+#define	INPUT_CD	2
+#define	INPUT_AUX	3
+#define	INPUT_MONO	4
+#define	INSRCS		0x1f		/* bits 0-4 */
+
+#define	DRVNAME		"audiosolo"
+
+static const char *solo_insrcs[] = {
+	AUDIO_PORT_MIC,
+	AUDIO_PORT_LINEIN,
+	AUDIO_PORT_CD,
+	AUDIO_PORT_AUX1IN,
+	AUDIO_PORT_AUX2IN,	/* this is really mono-in */
+	NULL
+};
+
+typedef struct solo_regs {
+	ddi_acc_handle_t	acch;
+	caddr_t			base;
+} solo_regs_t;
+
+typedef struct solo_engine {
+	struct solo_dev		*dev;
+	audio_engine_t		*engine;
+	ddi_dma_handle_t	dmah;
+	ddi_acc_handle_t	acch;
+	caddr_t			kaddr;
+	uint32_t		paddr;
+
+	bool			started;
+	uint64_t		count;
+	uint16_t		offset;
+	int			syncdir;
+	int			format;
+	bool			swapped;
+
+	void			(*start)(struct solo_engine *);
+	void			(*stop)(struct solo_engine *);
+	void			(*update)(struct solo_engine *);
+} solo_engine_t;
+
+typedef enum {
+	CTL_FRONT = 0,
+	CTL_VOLUME,
+	CTL_MIC,
+	CTL_LINE,
+	CTL_CD,
+	CTL_AUX,
+	CTL_MONO,
+	CTL_MICBOOST,
+	CTL_RECGAIN,
+	CTL_RECSRC,
+	CTL_MONSRC,
+	CTL_SPEAKER,
+	CTL_LOOPBACK,
+	CTL_NUM,			/* must be last */
+} solo_ctrl_num_t;
+
+typedef struct solo_ctrl {
+	struct solo_dev		*dev;
+	audio_ctrl_t		*ctrl;
+	solo_ctrl_num_t		num;
+	uint64_t		val;
+} solo_ctrl_t;
+
+typedef struct solo_dev {
+	dev_info_t		*dip;
+	audio_dev_t		*adev;
+	kmutex_t		mutex;
+	ddi_intr_handle_t	ihandle;
+
+	bool			suspended;
+
+	/*
+	 * Audio engines
+	 */
+	solo_engine_t		rec;
+	solo_engine_t		play;
+	uint32_t		last_capture;
+
+	/*
+	 * Controls.
+	 */
+	solo_ctrl_t		ctrls[CTL_NUM];
+
+	/*
+	 * Mapped registers
+	 */
+	ddi_acc_handle_t	pcih;
+	solo_regs_t		io;
+	solo_regs_t		sb;
+	solo_regs_t		vc;
+
+} solo_dev_t;
+
+/*
+ * Common code for the pcm function
+ *
+ * solo_cmd write a single byte to the CMD port.
+ * solo_cmd1 write a CMD + 1 byte arg
+ * ess_get_byte returns a single byte from the DSP data port
+ *
+ * solo_write is actually solo_cmd1
+ * solo_read access ext. regs via solo_cmd(0xc0, reg) followed by solo_get_byte
+ */
+
+#define	PORT_RD8(port, regno)		\
+	ddi_get8(port.acch, (void *)(port.base + (regno)))
+#define	PORT_RD16(port, regno)		\
+	ddi_get16(port.acch, (void *)(port.base + (regno)))
+#define	PORT_RD32(port, regno)		\
+	ddi_get32(port.acch, (void *)(port.base + (regno)))
+#define	PORT_WR8(port, regno, data)	\
+	ddi_put8(port.acch, (void *)(port.base + (regno)), data)
+#define	PORT_WR16(port, regno, data)	\
+	ddi_put16(port.acch, (void *)(port.base + (regno)), data)
+#define	PORT_WR32(port, regno, data)	\
+	ddi_put32(port.acch, (void *)(port.base + (regno)), data)
+
+static bool
+solo_dspready(solo_dev_t *dev)
+{
+	return ((PORT_RD8(dev->sb, 0xc) & 0x80) == 0 ? true : false);
+}
+
+static bool
+solo_dspwr(solo_dev_t *dev, uint8_t val)
+{
+	int  i;
+
+	for (i = 0; i < 1000; i++) {
+		if (solo_dspready(dev)) {
+			PORT_WR8(dev->sb, 0xc, val);
+			return (true);
+		}
+		if (i > 10)
+			drv_usecwait((i > 100)? 1000 : 10);
+	}
+	audio_dev_warn(dev->adev, "solo_dspwr(0x%02x) timed out", val);
+	return (false);
+}
+
+static bool
+solo_cmd(solo_dev_t *dev, uint8_t val)
+{
+	return (solo_dspwr(dev, val));
+}
+
+static void
+solo_cmd1(solo_dev_t *dev, uint8_t cmd, uint8_t val)
+{
+	if (solo_dspwr(dev, cmd)) {
+		(void) solo_dspwr(dev, val);
+	}
+}
+
+static void
+solo_setmixer(solo_dev_t *dev, uint8_t port, uint8_t value)
+{
+	PORT_WR8(dev->sb, 0x4, port); /* Select register */
+	drv_usecwait(10);
+	PORT_WR8(dev->sb, 0x5, value);
+	drv_usecwait(10);
+}
+
+static uint8_t
+solo_getmixer(solo_dev_t *dev, uint8_t port)
+{
+	uint8_t val;
+
+	PORT_WR8(dev->sb, 0x4, port); /* Select register */
+	drv_usecwait(10);
+	val = PORT_RD8(dev->sb, 0x5);
+	drv_usecwait(10);
+
+	return (val);
+}
+
+static uint8_t
+solo_get_byte(solo_dev_t *dev)
+{
+	for (int i = 1000; i > 0; i--) {
+		if (PORT_RD8(dev->sb, 0xc) & 0x40)
+			return (PORT_RD8(dev->sb, 0xa));
+		else
+			drv_usecwait(20);
+	}
+	audio_dev_warn(dev->adev, "timeout waiting to read DSP port");
+	return (0xff);
+}
+
+static void
+solo_write(solo_dev_t *dev, uint8_t reg, uint8_t val)
+{
+	solo_cmd1(dev, reg, val);
+}
+
+static uint8_t
+solo_read(solo_dev_t *dev, uint8_t reg)
+{
+	if (solo_cmd(dev, 0xc0) && solo_cmd(dev, reg)) {
+		return (solo_get_byte(dev));
+	}
+	return (0xff);
+}
+
+static bool
+solo_reset_dsp(solo_dev_t *dev)
+{
+	PORT_WR8(dev->sb, 0x6, 3);
+	drv_usecwait(100);
+	PORT_WR8(dev->sb, 0x6, 0);
+	if (solo_get_byte(dev) != 0xAA) {
+		audio_dev_warn(dev->adev, "solo_reset_dsp failed");
+		return (false);	/* Sorry */
+	}
+	return (true);
+}
+
+static uint_t
+solo_intr(caddr_t arg1, caddr_t arg2)
+{
+	solo_dev_t	*dev = (void *)arg1;
+	audio_engine_t	*prod = NULL;
+	audio_engine_t	*cons = NULL;
+	uint8_t		status;
+	uint_t		rv = DDI_INTR_UNCLAIMED;
+
+	_NOTE(ARGUNUSED(arg2));
+
+	mutex_enter(&dev->mutex);
+
+	if (dev->suspended) {
+		mutex_exit(&dev->mutex);
+		return (rv);
+	}
+
+	status = PORT_RD8(dev->io, 0x7);
+	if (status & 0x20) {
+		rv = DDI_INTR_CLAIMED;
+		cons = dev->play.engine;
+		/* ack the interrupt */
+		solo_setmixer(dev, 0x7a, solo_getmixer(dev, 0x7a) & ~0x80);
+	}
+
+	if (status & 0x10) {
+		rv = DDI_INTR_CLAIMED;
+		prod = dev->rec.engine;
+		/* ack the interrupt */
+		(void) PORT_RD8(dev->sb, 0xe);
+	}
+	mutex_exit(&dev->mutex);
+
+	if (cons) {
+		audio_engine_consume(cons);
+	}
+
+	if (prod) {
+		audio_engine_produce(prod);
+	}
+
+	return (rv);
+}
+
+static uint8_t
+solo_mixer_scale(solo_dev_t *dev, solo_ctrl_num_t num)
+{
+	uint32_t	l, r;
+	uint64_t	value = dev->ctrls[num].val;
+
+	l = (value >> 8) & 0xff;
+	r = value & 0xff;
+
+	l = (l * 15) / 100;
+	r = (r * 15) / 100;
+	return ((uint8_t)((l << 4) | (r)));
+}
+
+static void
+solo_configure_mixer(solo_dev_t *dev)
+{
+	uint32_t v;
+	uint32_t mon, rec;
+
+	/*
+	 * We disable hardware volume control (i.e. async updates to volume).
+	 * We could in theory support this, but making it work right can be
+	 * tricky, and we doubt it is widely used.
+	 */
+	solo_setmixer(dev, 0x64, solo_getmixer(dev, 0x64) | 0xc);
+	solo_setmixer(dev, 0x66, 0);
+
+	/* master volume has 6 bits per channel, bit 6 indicates mute  */
+	/* left */
+	v = (dev->ctrls[CTL_FRONT].val >> 8) & 0xff;
+	v = v ? (v * 63) / 100 : 64;
+	solo_setmixer(dev, 0x60, v & 0xff);
+
+	/* right */
+	v = dev->ctrls[CTL_FRONT].val & 0xff;
+	v = v ? (v * 63) / 100 : 64;
+	solo_setmixer(dev, 0x62, v & 0xff);
+
+	v = solo_mixer_scale(dev, CTL_VOLUME);
+	v = v | (v << 4);
+	solo_setmixer(dev, 0x7c, v & 0xff);
+	solo_setmixer(dev, 0x14, v & 0xff);
+
+	mon = dev->ctrls[CTL_MONSRC].val;
+	rec = dev->ctrls[CTL_RECSRC].val;
+
+	/*
+	 * The Solo-1 has dual stereo mixers (one for input and one for output),
+	 * with separate volume controls for each.
+	 */
+	v = solo_mixer_scale(dev, CTL_MIC);
+	solo_setmixer(dev, 0x68, rec & (1 << INPUT_MIC) ? v : 0);
+	solo_setmixer(dev, 0x1a, mon & (1 << INPUT_MIC) ? v : 0);
+
+	v = solo_mixer_scale(dev, CTL_LINE);
+	solo_setmixer(dev, 0x6e, rec & (1 << INPUT_LINE) ? v : 0);
+	solo_setmixer(dev, 0x3e, mon & (1 << INPUT_LINE) ? v : 0);
+
+	v = solo_mixer_scale(dev, CTL_CD);
+	solo_setmixer(dev, 0x6a, rec & (1 << INPUT_CD) ? v : 0);
+	solo_setmixer(dev, 0x38, mon & (1 << INPUT_CD) ? v : 0);
+
+	v = solo_mixer_scale(dev, CTL_AUX);
+	solo_setmixer(dev, 0x6c, rec & (1 << INPUT_AUX) ? v : 0);
+	solo_setmixer(dev, 0x3a, mon & (1 << INPUT_AUX) ? v : 0);
+
+	v = solo_mixer_scale(dev, CTL_MONO);
+	v = v | (v << 4);
+	solo_setmixer(dev, 0x6f, rec & (1 << INPUT_MONO) ? v : 0);
+	solo_setmixer(dev, 0x6d, mon & (1 << INPUT_MONO) ? v : 0);
+
+	if (dev->ctrls[CTL_MICBOOST].val) {
+		solo_setmixer(dev, 0x7d, solo_getmixer(dev, 0x7d) | 0x8);
+	} else {
+		solo_setmixer(dev, 0x7d, solo_getmixer(dev, 0x7d) & ~(0x8));
+	}
+
+	v = solo_mixer_scale(dev, CTL_RECGAIN);
+	v = v | (v << 4);
+	solo_write(dev, 0xb4, v & 0xff);
+
+	v = dev->ctrls[CTL_SPEAKER].val & 0xff;
+	v = (v * 7) / 100;
+	solo_setmixer(dev, 0x3c, v & 0xff);
+
+	if (dev->ctrls[CTL_LOOPBACK].val) {
+		/* record-what-you-hear mode */
+		solo_setmixer(dev, 0x1c, 0x3);
+	} else {
+		/* use record mixer */
+		solo_setmixer(dev, 0x1c, 0x5);
+	}
+
+}
+
+static int
+solo_set_mixsrc(void *arg, uint64_t val)
+{
+	solo_ctrl_t	*pc = arg;
+	solo_dev_t	*dev = pc->dev;
+
+	if ((val & ~INSRCS) != 0)
+		return (EINVAL);
+
+	mutex_enter(&dev->mutex);
+	pc->val = val;
+	if (!dev->suspended)
+		solo_configure_mixer(dev);
+	mutex_exit(&dev->mutex);
+	return (0);
+}
+
+static int
+solo_set_mono(void *arg, uint64_t val)
+{
+	solo_ctrl_t	*pc = arg;
+	solo_dev_t	*dev = pc->dev;
+
+	val &= 0xff;
+	if (val > 100)
+		return (EINVAL);
+
+	val = (val & 0xff) | ((val & 0xff) << 8);
+
+	mutex_enter(&dev->mutex);
+	pc->val = val;
+	if (!dev->suspended)
+		solo_configure_mixer(dev);
+	mutex_exit(&dev->mutex);
+	return (0);
+}
+
+static int
+solo_set_stereo(void *arg, uint64_t val)
+{
+	solo_ctrl_t	*pc = arg;
+	solo_dev_t	*dev = pc->dev;
+	uint8_t		l;
+	uint8_t		r;
+
+	l = (val & 0xff00) >> 8;
+	r = val & 0xff;
+
+	if ((l > 100) || (r > 100))
+		return (EINVAL);
+
+	mutex_enter(&dev->mutex);
+	pc->val = val;
+	if (!dev->suspended)
+		solo_configure_mixer(dev);
+	mutex_exit(&dev->mutex);
+	return (0);
+}
+
+static int
+solo_set_bool(void *arg, uint64_t val)
+{
+	solo_ctrl_t	*pc = arg;
+	solo_dev_t	*dev = pc->dev;
+
+	mutex_enter(&dev->mutex);
+	pc->val = val;
+	if (!dev->suspended)
+		solo_configure_mixer(dev);
+	mutex_exit(&dev->mutex);
+	return (0);
+}
+
+static int
+solo_get_value(void *arg, uint64_t *val)
+{
+	solo_ctrl_t	*pc = arg;
+	solo_dev_t	*dev = pc->dev;
+
+	mutex_enter(&dev->mutex);
+	*val = pc->val;
+	mutex_exit(&dev->mutex);
+	return (0);
+}
+
+#define	PLAYCTL	(AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_PLAY)
+#define	RECCTL	(AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_REC)
+#define	MONCTL	(AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_MONITOR)
+#define	PCMVOL	(PLAYCTL | AUDIO_CTRL_FLAG_PCMVOL)
+#define	MAINVOL	(PLAYCTL | AUDIO_CTRL_FLAG_MAINVOL)
+#define	RECVOL	(RECCTL | AUDIO_CTRL_FLAG_RECVOL)
+
+static void
+solo_alloc_ctrl(solo_dev_t *dev, uint32_t num, uint64_t val)
+{
+	audio_ctrl_desc_t	desc;
+	audio_ctrl_wr_t		fn;
+	solo_ctrl_t		*pc;
+
+	bzero(&desc, sizeof (desc));
+
+	pc = &dev->ctrls[num];
+	pc->num = num;
+	pc->dev = dev;
+
+	switch (num) {
+	case CTL_VOLUME:
+		desc.acd_name = AUDIO_CTRL_ID_VOLUME;
+		desc.acd_type = AUDIO_CTRL_TYPE_MONO;
+		desc.acd_minvalue = 0;
+		desc.acd_maxvalue = 100;
+		desc.acd_flags = PCMVOL;
+		fn = solo_set_mono;
+		break;
+
+	case CTL_FRONT:
+		desc.acd_name = AUDIO_CTRL_ID_LINEOUT;
+		desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
+		desc.acd_minvalue = 0;
+		desc.acd_maxvalue = 100;
+		desc.acd_flags = MAINVOL;
+		fn = solo_set_stereo;
+		break;
+
+	case CTL_SPEAKER:
+		desc.acd_name = AUDIO_CTRL_ID_SPEAKER;
+		desc.acd_type = AUDIO_CTRL_TYPE_MONO;
+		desc.acd_minvalue = 0;
+		desc.acd_maxvalue = 100;
+		desc.acd_flags = MAINVOL;
+		fn = solo_set_mono;
+		break;
+
+	case CTL_MIC:
+		desc.acd_name = AUDIO_CTRL_ID_MIC;
+		desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
+		desc.acd_minvalue = 0;
+		desc.acd_maxvalue = 100;
+		desc.acd_flags = RECVOL;
+		fn = solo_set_stereo;
+		break;
+
+	case CTL_LINE:
+		desc.acd_name = AUDIO_CTRL_ID_LINEIN;
+		desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
+		desc.acd_minvalue = 0;
+		desc.acd_maxvalue = 100;
+		desc.acd_flags = RECVOL;
+		fn = solo_set_stereo;
+		break;
+
+	case CTL_CD:
+		desc.acd_name = AUDIO_CTRL_ID_CD;
+		desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
+		desc.acd_minvalue = 0;
+		desc.acd_maxvalue = 100;
+		desc.acd_flags = RECVOL;
+		fn = solo_set_stereo;
+		break;
+
+	case CTL_AUX:
+		desc.acd_name = AUDIO_CTRL_ID_AUX1IN;
+		desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
+		desc.acd_minvalue = 0;
+		desc.acd_maxvalue = 100;
+		desc.acd_flags = RECVOL;
+		fn = solo_set_stereo;
+		break;
+
+	case CTL_MONO:
+		desc.acd_name = AUDIO_CTRL_ID_AUX2IN;
+		desc.acd_type = AUDIO_CTRL_TYPE_MONO;
+		desc.acd_minvalue = 0;
+		desc.acd_maxvalue = 100;
+		desc.acd_flags = RECVOL;
+		fn = solo_set_mono;
+		break;
+
+	case CTL_RECSRC:
+		desc.acd_name = AUDIO_CTRL_ID_RECSRC;
+		desc.acd_type = AUDIO_CTRL_TYPE_ENUM;
+		desc.acd_minvalue = INSRCS;
+		desc.acd_maxvalue = INSRCS;
+		desc.acd_flags = RECCTL | AUDIO_CTRL_FLAG_MULTI;
+		for (int i = 0; solo_insrcs[i]; i++) {
+			desc.acd_enum[i] = solo_insrcs[i];
+		}
+		fn = solo_set_mixsrc;
+		break;
+
+	case CTL_MONSRC:
+		desc.acd_name = AUDIO_CTRL_ID_MONSRC;
+		desc.acd_type = AUDIO_CTRL_TYPE_ENUM;
+		desc.acd_minvalue = INSRCS;
+		desc.acd_maxvalue = INSRCS;
+		desc.acd_flags = MONCTL | AUDIO_CTRL_FLAG_MULTI;
+		for (int i = 0; solo_insrcs[i]; i++) {
+			desc.acd_enum[i] = solo_insrcs[i];
+		}
+		fn = solo_set_mixsrc;
+		break;
+
+	case CTL_MICBOOST:
+		desc.acd_name = AUDIO_CTRL_ID_MICBOOST;
+		desc.acd_type = AUDIO_CTRL_TYPE_BOOLEAN;
+		desc.acd_minvalue = 0;
+		desc.acd_maxvalue = 1;
+		desc.acd_flags = RECCTL;
+		fn = solo_set_bool;
+		break;
+
+	case CTL_LOOPBACK:
+		desc.acd_name = AUDIO_CTRL_ID_LOOPBACK;
+		desc.acd_type = AUDIO_CTRL_TYPE_BOOLEAN;
+		desc.acd_minvalue = 0;
+		desc.acd_maxvalue = 1;
+		desc.acd_flags = RECCTL;
+		fn = solo_set_bool;
+		break;
+
+	case CTL_RECGAIN:
+		desc.acd_name = AUDIO_CTRL_ID_RECGAIN;
+		desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
+		desc.acd_minvalue = 0;
+		desc.acd_maxvalue = 100;
+		desc.acd_flags = RECCTL;
+		fn = solo_set_stereo;
+		break;
+	}
+
+	pc->val = val;
+	pc->ctrl = audio_dev_add_control(dev->adev, &desc,
+	    solo_get_value, fn, pc);
+}
+
+static bool
+solo_add_controls(solo_dev_t *dev)
+{
+	solo_alloc_ctrl(dev, CTL_VOLUME, 0x4b);
+	solo_alloc_ctrl(dev, CTL_FRONT, 0x5a5a);
+	solo_alloc_ctrl(dev, CTL_SPEAKER, 0x4b);
+	solo_alloc_ctrl(dev, CTL_MIC, 0x3232);
+	solo_alloc_ctrl(dev, CTL_LINE, 0x4b4b);
+	solo_alloc_ctrl(dev, CTL_CD, 0x4b4b);
+	solo_alloc_ctrl(dev, CTL_AUX, 0);
+	solo_alloc_ctrl(dev, CTL_MONO, 0);
+	solo_alloc_ctrl(dev, CTL_RECSRC, (1U << INPUT_MIC));
+	solo_alloc_ctrl(dev, CTL_MONSRC, 0);
+	solo_alloc_ctrl(dev, CTL_RECGAIN, 0x4b4b);
+	solo_alloc_ctrl(dev, CTL_MICBOOST, 1);
+	solo_alloc_ctrl(dev, CTL_LOOPBACK, 0);
+
+	return (true);
+}
+
+
+/* utility functions for ESS */
+static uint8_t
+solo_calcfilter(int spd)
+{
+	int cutoff;
+
+	cutoff = (spd * 9 * 82) / 20;
+	return (256 - (7160000 / cutoff));
+}
+
+static void
+solo_aud1_update(solo_engine_t *e)
+{
+	solo_dev_t	*dev = e->dev;
+	uint16_t	offset, n;
+	uint32_t	ptr;
+	uint32_t	count;
+	uint32_t	diff;
+
+	ASSERT(mutex_owned(&dev->mutex));
+
+	/*
+	 * During recording, this register is known to give back
+	 * garbage if it's not quiescent while being read.  This hack
+	 * attempts to work around it.
+	 */
+	ptr = PORT_RD32(dev->vc, 0);
+	count = PORT_RD16(dev->vc, 4);
+	diff = e->paddr + SOLO_BUFSZ - ptr - count;
+	if ((diff > 3) || (ptr < e->paddr) ||
+	    (ptr >= (e->paddr + SOLO_BUFSZ))) {
+		ptr = dev->last_capture;
+	} else {
+		dev->last_capture = ptr;
+	}
+	offset = ptr - e->paddr;
+	offset /= (SOLO_NCHAN * SOLO_SAMPSZ);
+
+	n = offset >= e->offset ?
+	    offset - e->offset :
+	    offset + SOLO_BUFSZ - e->offset;
+
+	e->offset = offset;
+	e->count += n / (SOLO_NCHAN * SOLO_SAMPSZ);
+}
+
+static void
+solo_aud1_start(solo_engine_t *e)
+{
+	solo_dev_t	*dev = e->dev;
+	int		len;
+	uint32_t	v;
+
+	ASSERT(mutex_owned(&dev->mutex));
+
+	len = SOLO_FRAGSZ / 2;
+	len = -len;
+
+	/* sample rate - 48 kHz */
+	solo_write(dev, 0xa1, 0xf0);
+	/* filter cutoff */
+	solo_write(dev, 0xa2, solo_calcfilter(SOLO_RATE));
+
+
+	/* mono/stereo - bit 0 set, bit 1 clear */
+	solo_write(dev, 0xa8, (solo_read(dev, 0xa8) & ~0x03) | 1);
+
+	(void) solo_cmd(dev, 0xd3);	/* turn off DAC1 output */
+
+	/* setup fifo for signed 16-bit stereo */
+	solo_write(dev, 0xb7, 0x71);
+	solo_write(dev, 0xb7, 0xbc);
+
+	v = solo_mixer_scale(dev, CTL_RECGAIN);
+	v = v | (v << 4);
+	solo_write(dev, 0xb4, v & 0xff);
+
+	PORT_WR8(dev->vc, 0x8, 0xc4); /* command */
+	PORT_WR8(dev->vc, 0xd, 0xff); /* clear DMA */
+	PORT_WR8(dev->vc, 0xf, 0x01); /* stop DMA  */
+
+	PORT_WR8(dev->vc, 0xd, 0xff); /* reset */
+	PORT_WR8(dev->vc, 0xf, 0x01); /* mask */
+	PORT_WR8(dev->vc, 0xb, 0x14); /* mode */
+
+	PORT_WR32(dev->vc, 0x0, e->paddr);
+	PORT_WR16(dev->vc, 0x4, SOLO_BUFSZ - 1);
+
+	/* transfer length low, high */
+	solo_write(dev, 0xa4, len & 0x00ff);
+	solo_write(dev, 0xa5, (len & 0xff00) >> 8);
+
+	/* autoinit, dma dir, go for it */
+	solo_write(dev, 0xb8, 0x0f);
+	PORT_WR8(dev->vc, 0xf, 0);	/* start DMA */
+
+	dev->last_capture = e->paddr;
+}
+
+static void
+solo_aud1_stop(solo_engine_t *e)
+{
+	solo_dev_t	*dev = e->dev;
+
+	/* NB: We might be in quiesce, without a lock held */
+	solo_write(dev, 0xb8, solo_read(dev, 0xb8) & ~0x01);
+}
+
+static void
+solo_aud2_update(solo_engine_t *e)
+{
+	solo_dev_t	*dev = e->dev;
+	uint16_t	offset = 0, n;
+
+	ASSERT(mutex_owned(&dev->mutex));
+
+	offset = SOLO_BUFSZ - PORT_RD16(dev->io, 0x4);
+	offset /= (SOLO_NCHAN * SOLO_SAMPSZ);
+
+	n = offset >= e->offset ?
+	    offset - e->offset :
+	    offset + SOLO_BUFFR - e->offset;
+
+	e->offset = offset;
+	e->count += n;
+}
+
+static void
+solo_aud2_start(solo_engine_t *e)
+{
+	solo_dev_t	*dev = e->dev;
+	int		len;
+	uint32_t	v;
+
+	ASSERT(mutex_owned(&dev->mutex));
+
+	len = SOLO_FRAGSZ / 2;
+	len = -len;
+
+	/* program transfer type */
+	solo_setmixer(dev, 0x78, 0x10);
+	/* sample rate - 48 kHz */
+	solo_setmixer(dev, 0x70, 0xf0);
+	solo_setmixer(dev, 0x72, solo_calcfilter(SOLO_RATE));
+	/* transfer length low & high */
+	solo_setmixer(dev, 0x74, len & 0x00ff);
+	solo_setmixer(dev, 0x76, (len & 0xff00) >> 8);
+	/* enable irq, set signed 16-bit stereo format */
+	solo_setmixer(dev, 0x7a, 0x47);
+
+	PORT_WR8(dev->io, 0x6, 0);
+	PORT_WR32(dev->io, 0x0, e->paddr);
+	PORT_WR16(dev->io, 0x4, SOLO_BUFSZ);
+
+	/* this crazy initialization appears to help with fifo weirdness */
+	/* start the engine running */
+	solo_setmixer(dev, 0x78, 0x92);
+	drv_usecwait(10);
+	solo_setmixer(dev, 0x78, 0x93);
+
+	PORT_WR8(dev->io, 0x6, 0x0a); /* autoinit, enable */
+
+	v = solo_mixer_scale(dev, CTL_VOLUME);
+	v = v | (v << 4);
+	solo_setmixer(dev, 0x7c, v & 0xff);
+}
+
+static void
+solo_aud2_stop(solo_engine_t *e)
+{
+	solo_dev_t	*dev = e->dev;
+
+	/* NB: We might be in quiesce, without a lock held */
+	PORT_WR8(dev->io, 0x6, 0);
+	solo_setmixer(dev, 0x78, solo_getmixer(dev, 0x78) & ~0x03);
+}
+
+/*
+ * Audio entry points.
+ */
+static int
+solo_format(void *arg)
+{
+	solo_engine_t	*e = arg;
+	return (e->format);
+}
+
+static int
+solo_channels(void *arg)
+{
+	_NOTE(ARGUNUSED(arg));
+	return (SOLO_NCHAN);
+}
+
+static int
+solo_rate(void *arg)
+{
+	_NOTE(ARGUNUSED(arg));
+	return (SOLO_RATE);
+}
+
+static size_t
+solo_qlen(void *arg)
+{
+	_NOTE(ARGUNUSED(arg));
+	return (0);
+}
+
+static void
+solo_chinfo(void *arg, int chan, unsigned *offset, unsigned *incr)
+{
+	solo_engine_t *e = arg;
+
+	if (e->swapped) {
+		*offset = !chan;
+	} else {
+		*offset = chan;
+	}
+	*incr = 2;
+}
+
+static void
+solo_sync(void *arg, unsigned nframes)
+{
+	solo_engine_t *e = arg;
+
+	_NOTE(ARGUNUSED(nframes));
+
+	(void) ddi_dma_sync(e->dmah, 0, 0, e->syncdir);
+}
+
+
+static uint64_t
+solo_count(void *arg)
+{
+	solo_engine_t	*e = arg;
+	solo_dev_t	*dev = e->dev;
+	uint64_t	count;
+
+	mutex_enter(&dev->mutex);
+	if (!dev->suspended)
+		e->update(e);
+	count = e->count;
+	mutex_exit(&dev->mutex);
+
+	return (count);
+}
+
+static int
+solo_open(void *arg, int f, unsigned *ffr, unsigned *nfr, caddr_t *buf)
+{
+	solo_engine_t	*e = arg;
+	solo_dev_t	*dev = e->dev;
+
+	_NOTE(ARGUNUSED(f));
+
+	/* NB: For simplicity, we just fix the interrupt rate at 100 Hz */
+	*ffr = SOLO_FRAGFR;
+	*nfr = SOLO_NFRAGS;
+	*buf = e->kaddr;
+
+	mutex_enter(&dev->mutex);
+	e->started = false;
+	e->count = 0;
+	mutex_exit(&dev->mutex);
+
+	return (0);
+}
+
+void
+solo_close(void *arg)
+{
+	solo_engine_t	*e = arg;
+	solo_dev_t	*dev = e->dev;
+
+	mutex_enter(&dev->mutex);
+	if (!dev->suspended)
+		e->stop(e);
+	e->started = false;
+	mutex_exit(&dev->mutex);
+}
+
+
+static int
+solo_start(void *arg)
+{
+	solo_engine_t	*e = arg;
+	solo_dev_t	*dev = e->dev;
+
+	mutex_enter(&dev->mutex);
+	if (!e->started) {
+		if (!dev->suspended)
+			e->start(e);
+		e->started = true;
+	}
+	mutex_exit(&dev->mutex);
+
+	return (0);
+}
+
+static void
+solo_stop(void *arg)
+{
+	solo_engine_t	*e = arg;
+	solo_dev_t	*dev = e->dev;
+
+	mutex_enter(&dev->mutex);
+	if (e->started) {
+		if (!dev->suspended)
+			e->stop(e);
+		e->started = false;
+	}
+	mutex_exit(&dev->mutex);
+
+}
+
+static audio_engine_ops_t solo_engine_ops = {
+	AUDIO_ENGINE_VERSION,
+	solo_open,
+	solo_close,
+	solo_start,
+	solo_stop,
+	solo_count,
+	solo_format,
+	solo_channels,
+	solo_rate,
+	solo_sync,
+	solo_qlen,
+	solo_chinfo,
+};
+
+static void
+solo_release_resources(solo_dev_t *dev)
+{
+	if (dev->ihandle != NULL) {
+		(void) ddi_intr_disable(dev->ihandle);
+		(void) ddi_intr_remove_handler(dev->ihandle);
+		(void) ddi_intr_free(dev->ihandle);
+		mutex_destroy(&dev->mutex);
+	}
+
+	if (dev->io.acch != NULL) {
+		ddi_regs_map_free(&dev->io.acch);
+	}
+
+	if (dev->sb.acch != NULL) {
+		ddi_regs_map_free(&dev->sb.acch);
+	}
+
+	if (dev->vc.acch != NULL) {
+		ddi_regs_map_free(&dev->vc.acch);
+	}
+
+	if (dev->pcih != NULL) {
+		pci_config_teardown(&dev->pcih);
+	}
+
+	/* release play resources */
+	if (dev->play.paddr != 0)
+		(void) ddi_dma_unbind_handle(dev->play.dmah);
+	if (dev->play.acch != NULL)
+		ddi_dma_mem_free(&dev->play.acch);
+	if (dev->play.dmah != NULL)
+		ddi_dma_free_handle(&dev->play.dmah);
+
+	if (dev->play.engine != NULL) {
+		audio_dev_remove_engine(dev->adev, dev->play.engine);
+		audio_engine_free(dev->play.engine);
+	}
+
+	/* release record resources */
+	if (dev->rec.paddr != 0)
+		(void) ddi_dma_unbind_handle(dev->rec.dmah);
+	if (dev->rec.acch != NULL)
+		ddi_dma_mem_free(&dev->rec.acch);
+	if (dev->rec.dmah != NULL)
+		ddi_dma_free_handle(&dev->rec.dmah);
+
+	if (dev->rec.engine != NULL) {
+		audio_dev_remove_engine(dev->adev, dev->rec.engine);
+		audio_engine_free(dev->rec.engine);
+	}
+
+	if (dev->adev != NULL) {
+		audio_dev_free(dev->adev);
+	}
+
+	kmem_free(dev, sizeof (*dev));
+}
+
+static bool
+solo_setup_interrupts(solo_dev_t *dev)
+{
+	int actual;
+	uint_t ipri;
+
+	if ((ddi_intr_alloc(dev->dip, &dev->ihandle, DDI_INTR_TYPE_FIXED,
+	    0, 1, &actual, DDI_INTR_ALLOC_NORMAL) != DDI_SUCCESS) ||
+	    (actual != 1)) {
+		audio_dev_warn(dev->adev, "can't alloc intr handle");
+		return (false);
+	}
+
+	if (ddi_intr_get_pri(dev->ihandle, &ipri) != DDI_SUCCESS) {
+		audio_dev_warn(dev->adev,  "can't determine intr priority");
+		(void) ddi_intr_free(dev->ihandle);
+		dev->ihandle = NULL;
+		return (false);
+	}
+
+	if (ddi_intr_add_handler(dev->ihandle, solo_intr, dev,
+	    NULL) != DDI_SUCCESS) {
+		audio_dev_warn(dev->adev, "can't add intr handler");
+		(void) ddi_intr_free(dev->ihandle);
+		dev->ihandle = NULL;
+		return (false);
+	}
+
+	mutex_init(&dev->mutex, NULL, MUTEX_DRIVER, DDI_INTR_PRI(ipri));
+
+	return (true);
+}
+
+static bool
+solo_map_registers(solo_dev_t *dev)
+{
+	dev_info_t	*dip = dev->dip;
+
+	/* map registers */
+	if (ddi_regs_map_setup(dip, 1, &dev->io.base, 0, 0, &acc_attr,
+	    &dev->io.acch) != DDI_SUCCESS) {
+		audio_dev_warn(dev->adev, "can't map IO registers");
+		return (false);
+	}
+	if (ddi_regs_map_setup(dip, 2, &dev->sb.base, 0, 0, &acc_attr,
+	    &dev->sb.acch) != DDI_SUCCESS) {
+		audio_dev_warn(dev->adev, "can't map SB registers");
+		return (false);
+	}
+	if (ddi_regs_map_setup(dip, 3, &dev->vc.base, 0, 0, &acc_attr,
+	    &dev->vc.acch) != DDI_SUCCESS) {
+		audio_dev_warn(dev->adev, "can't map VC registers");
+		return (false);
+	}
+
+	return (true);
+}
+
+#define	ESS_PCI_LEGACYCONTROL		0x40
+#define	ESS_PCI_CONFIG			0x50
+#define	ESS_PCI_DDMACONTROL		0x60
+
+static bool
+solo_init_hw(solo_dev_t *dev)
+{
+	uint32_t	data;
+
+	/*
+	 * Legacy audio register -- disable legacy audio.  We also
+	 * arrange for 16-bit I/O address decoding.
+	 */
+	/* this version disables the MPU, FM synthesis (Adlib), and Game Port */
+	pci_config_put16(dev->pcih, ESS_PCI_LEGACYCONTROL, 0x8041);
+
+	/*
+	 * Note that Solo-1 uses I/O space for all BARs, and hardwires
+	 * the upper 32-bits to zero.
+	 */
+	data = pci_config_get32(dev->pcih, PCI_CONF_BASE2);
+	data |= 1;
+	pci_config_put16(dev->pcih, ESS_PCI_DDMACONTROL, data & 0xffff);
+
+	/*
+	 * Make sure that legacy IRQ and DRQ are disbled.  We disable most
+	 * other legacy features too.
+	 */
+	pci_config_put16(dev->pcih, ESS_PCI_CONFIG, 0);
+
+	if (!solo_reset_dsp(dev))
+		return (false);
+
+	/* enable extended mode */
+	(void) solo_cmd(dev, 0xc6);
+
+
+	PORT_WR8(dev->io, 0x7, 0x30); /* enable audio irqs */
+
+	/* demand mode, 4 bytes/xfer */
+	solo_write(dev, 0xb9, 0x01);
+
+	/*
+	 * This sets Audio 2 (playback) to use its own independent
+	 * rate control, and gives us 48 kHz compatible divisors.  It
+	 * also bypasses the switched capacitor filter.
+	 */
+	solo_setmixer(dev, 0x71, 0x2a);
+
+	/* irq control */
+	solo_write(dev, 0xb1, (solo_read(dev, 0xb1) & 0x0f) | 0x50);
+	/* drq control */
+	solo_write(dev, 0xb2, (solo_read(dev, 0xb2) & 0x0f) | 0x50);
+
+	solo_setmixer(dev, 0, 0); /* reset mixer settings */
+
+	solo_configure_mixer(dev);
+	return (true);
+}
+
+static bool
+solo_alloc_engine(solo_dev_t *dev, int engno)
+{
+	size_t			rlen;
+	ddi_dma_attr_t		*dattr;
+	ddi_dma_cookie_t	c;
+	unsigned		ccnt;
+	unsigned		caps;
+	unsigned		dflags;
+	const char		*desc;
+	solo_engine_t		*e;
+
+	ASSERT((engno == 1) || (engno = 2));
+
+	switch (engno) {
+	case 1:	/* record */
+		e = &dev->rec;
+		desc = "record";
+		dattr = &dma_attr_audio1;
+		caps = ENGINE_INPUT_CAP;
+		dflags = DDI_DMA_READ | DDI_DMA_CONSISTENT;
+		e->syncdir = DDI_DMA_SYNC_FORKERNEL;
+		e->update = solo_aud1_update;
+		e->start = solo_aud1_start;
+		e->stop = solo_aud1_stop;
+		e->format = AUDIO_FORMAT_S16_BE;
+		e->swapped = true;
+		break;
+
+	case 2:	/* playback */
+		e = &dev->play;
+		desc = "playback";
+		dattr = &dma_attr_audio2;
+		caps = ENGINE_OUTPUT_CAP;
+		dflags = DDI_DMA_WRITE | DDI_DMA_CONSISTENT;
+		e->syncdir = DDI_DMA_SYNC_FORDEV;
+		e->update = solo_aud2_update;
+		e->start = solo_aud2_start;
+		e->stop = solo_aud2_stop;
+		e->format = AUDIO_FORMAT_S16_LE;
+		e->swapped = false;
+		break;
+
+	default:
+		audio_dev_warn(dev->adev, "bad engine number!");
+		return (false);
+	}
+
+	printf("%s %sswapped!", desc, e->swapped ? "" : "not ");
+	e->dev = dev;
+
+	if (ddi_dma_alloc_handle(dev->dip, dattr, DDI_DMA_SLEEP, NULL,
+	    &e->dmah) != DDI_SUCCESS) {
+		audio_dev_warn(dev->adev, "%s dma handle alloc failed", desc);
+		return (false);
+	}
+	if (ddi_dma_mem_alloc(e->dmah, SOLO_BUFSZ, &buf_attr,
+	    DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &e->kaddr,
+	    &rlen, &e->acch) != DDI_SUCCESS) {
+		audio_dev_warn(dev->adev, "%s dma memory alloc failed", desc);
+		return (false);
+	}
+	/* ensure that the buffer is zeroed out properly */
+	bzero(e->kaddr, rlen);
+	if (ddi_dma_addr_bind_handle(e->dmah, NULL, e->kaddr, SOLO_BUFSZ,
+	    dflags, DDI_DMA_SLEEP, NULL, &c, &ccnt) != DDI_DMA_MAPPED) {
+		audio_dev_warn(dev->adev, "%s dma binding failed", desc);
+		return (false);
+	}
+	e->paddr = c.dmac_address;
+
+	/*
+	 * Allocate and configure audio engine.
+	 */
+	e->engine = audio_engine_alloc(&solo_engine_ops, caps);
+	if (e->engine == NULL) {
+		audio_dev_warn(dev->adev, "record audio_engine_alloc failed");
+		return (false);
+	}
+
+	audio_engine_set_private(e->engine, e);
+	audio_dev_add_engine(dev->adev, e->engine);
+
+	return (true);
+}
+
+
+static int
+solo_suspend(solo_dev_t *dev)
+{
+	mutex_enter(&dev->mutex);
+	/* play */
+	solo_aud2_stop(&dev->play);
+	solo_aud2_update(&dev->play);
+	/* record */
+	solo_aud1_stop(&dev->rec);
+	solo_aud1_update(&dev->rec);
+
+	dev->suspended = true;
+	mutex_exit(&dev->mutex);
+
+	return (DDI_SUCCESS);
+}
+
+static int
+solo_resume(solo_dev_t *dev)
+{
+	solo_engine_t	*e;
+	audio_engine_t	*prod = NULL;
+	audio_engine_t	*cons = NULL;
+
+	audio_engine_reset(dev->rec.engine);
+	audio_engine_reset(dev->play.engine);
+
+	mutex_enter(&dev->mutex);
+	if (!solo_init_hw(dev)) {
+		/* yikes! */
+		audio_dev_warn(dev->adev, "unable to resume audio!");
+		audio_dev_warn(dev->adev, "reboot or reload driver to reset");
+		mutex_exit(&dev->mutex);
+		return (DDI_SUCCESS);
+	}
+	dev->suspended = false;
+
+	/* record - audio 1 */
+	e = &dev->rec;
+	if (e->started) {
+		e->start(e);
+		prod = e->engine;
+	}
+
+	/* play - audio 2 */
+	e = &dev->play;
+	if (e->started) {
+		e->start(e);
+		cons = e->engine;
+	}
+
+	mutex_exit(&dev->mutex);
+
+	if (cons)
+		audio_engine_consume(cons);
+	if (prod)
+		audio_engine_produce(prod);
+
+	return (DDI_SUCCESS);
+}
+
+static int
+solo_attach(dev_info_t *dip)
+{
+	solo_dev_t	*dev;
+	uint32_t	data;
+
+	dev = kmem_zalloc(sizeof (*dev), KM_SLEEP);
+	dev->dip = dip;
+	ddi_set_driver_private(dip, dev);
+
+	dev->adev = audio_dev_alloc(dip, 0);
+	if (dev->adev == NULL)
+		goto no;
+
+	audio_dev_set_description(dev->adev, "ESS Solo-1 PCI AudioDrive");
+	audio_dev_set_version(dev->adev, "ES1938");
+
+	if (pci_config_setup(dip, &dev->pcih) != DDI_SUCCESS) {
+		audio_dev_warn(NULL, "pci_config_setup failed");
+		goto no;
+	}
+
+	data = pci_config_get16(dev->pcih, PCI_CONF_COMM);
+	data |= PCI_COMM_ME | PCI_COMM_IO;
+	pci_config_put16(dev->pcih, PCI_CONF_COMM, data);
+
+	if ((!solo_map_registers(dev)) ||
+	    (!solo_setup_interrupts(dev)) ||
+	    (!solo_alloc_engine(dev, 1)) ||
+	    (!solo_alloc_engine(dev, 2)) ||
+	    (!solo_add_controls(dev)) ||
+	    (!solo_init_hw(dev))) {
+		goto no;
+	}
+
+	if (audio_dev_register(dev->adev) != DDI_SUCCESS) {
+		audio_dev_warn(dev->adev,
+		    "unable to register with audio framework");
+		goto no;
+	}
+
+	(void) ddi_intr_enable(dev->ihandle);
+	ddi_report_dev(dip);
+
+	return (DDI_SUCCESS);
+
+no:
+	solo_release_resources(dev);
+	return (DDI_FAILURE);
+}
+
+static int
+solo_detach(solo_dev_t *dev)
+{
+	if (audio_dev_unregister(dev->adev) != DDI_SUCCESS) {
+		return (DDI_FAILURE);
+	}
+
+	solo_release_resources(dev);
+	return (DDI_SUCCESS);
+}
+
+static int
+solo_ddi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+	solo_dev_t *dev;
+
+	switch (cmd) {
+	case DDI_ATTACH:
+		return (solo_attach(dip));
+
+	case DDI_RESUME:
+		if ((dev = ddi_get_driver_private(dip)) == NULL) {
+			return (DDI_FAILURE);
+		}
+		return (solo_resume(dev));
+
+	default:
+		return (DDI_FAILURE);
+	}
+}
+
+static int
+solo_ddi_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+	solo_dev_t *dev;
+
+	if ((dev = ddi_get_driver_private(dip)) == NULL) {
+		return (DDI_FAILURE);
+	}
+
+	switch (cmd) {
+	case DDI_DETACH:
+		return (solo_detach(dev));
+
+	case DDI_SUSPEND:
+		return (solo_suspend(dev));
+	default:
+		return (DDI_FAILURE);
+	}
+}
+
+static int
+solo_quiesce(dev_info_t *dip)
+{
+	solo_dev_t *dev;
+
+	dev = ddi_get_driver_private(dip);
+
+	solo_aud1_stop(&dev->rec);
+	solo_aud2_stop(&dev->play);
+
+	solo_setmixer(dev, 0, 0);
+	PORT_WR8(dev->io, 0x7, 0); /* disable all irqs */
+	return (0);
+}
+
+struct dev_ops solo_dev_ops = {
+	DEVO_REV,		/* rev */
+	0,			/* refcnt */
+	NULL,			/* getinfo */
+	nulldev,		/* identify */
+	nulldev,		/* probe */
+	solo_ddi_attach,	/* attach */
+	solo_ddi_detach,	/* detach */
+	nodev,			/* reset */
+	NULL,			/* cb_ops */
+	NULL,			/* bus_ops */
+	NULL,			/* power */
+	solo_quiesce,		/* quiesce */
+};
+
+static struct modldrv solo_modldrv = {
+	&mod_driverops,			/* drv_modops */
+	"ESS Solo-1 Audio",		/* linkinfo */
+	&solo_dev_ops,			/* dev_ops */
+};
+
+static struct modlinkage modlinkage = {
+	MODREV_1,
+	{ &solo_modldrv, NULL }
+};
+
+int
+_init(void)
+{
+	int	rv;
+
+	audio_init_ops(&solo_dev_ops, DRVNAME);
+	if ((rv = mod_install(&modlinkage)) != 0) {
+		audio_fini_ops(&solo_dev_ops);
+	}
+	return (rv);
+}
+
+int
+_fini(void)
+{
+	int	rv;
+
+	if ((rv = mod_remove(&modlinkage)) == 0) {
+		audio_fini_ops(&solo_dev_ops);
+	}
+	return (rv);
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+	return (mod_info(&modlinkage, modinfop));
+}
--- a/usr/src/uts/intel/Makefile.intel.shared	Tue Sep 15 19:09:53 2009 -0700
+++ b/usr/src/uts/intel/Makefile.intel.shared	Tue Sep 15 21:03:09 2009 -0700
@@ -206,6 +206,7 @@
 DRV_KMODS	+= audiols
 DRV_KMODS	+= audiop16x
 DRV_KMODS	+= audiopci
+DRV_KMODS	+= audiosolo
 DRV_KMODS	+= audiots
 DRV_KMODS	+= audiovia823x
 DRV_KMODS_32	+= audiovia97
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/intel/audiosolo/Makefile	Tue Sep 15 21:03:09 2009 -0700
@@ -0,0 +1,81 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# uts/intel/audiosolo/Makefile
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+#	This makefile drives the production of the audiosolo driver.
+#
+
+#
+#	Path to the base of the uts directory tree (usually /usr/src/uts).
+#
+UTSBASE = ../..
+
+#
+#	Define the module and object file sets.
+#
+MODULE		= audiosolo
+OBJECTS		= $(AUDIOSOLO_OBJS:%=$(OBJS_DIR)/%)
+LINTS		= $(AUDIOSOLO_OBJS:%.o=$(LINTS_DIR)/%.ln)
+ROOTMODULE	= $(ROOT_DRV_DIR)/$(MODULE)
+
+#
+#	Include common rules.
+#
+include $(UTSBASE)/intel/Makefile.intel
+
+#
+#	Define targets
+#
+ALL_TARGET	= $(BINARY)
+LINT_TARGET	= $(MODULE).lint
+INSTALL_TARGET	= $(BINARY) $(ROOTMODULE)
+
+LDFLAGS		+= -dy -Ndrv/audio
+
+#
+#	Default build targets.
+#
+.KEEP_STATE:
+
+def:		$(DEF_DEPS)
+
+all:		$(ALL_DEPS)
+
+clean:		$(CLEAN_DEPS)
+
+clobber:	$(CLOBBER_DEPS)
+
+lint:		$(LINT_DEPS)
+
+modlintlib:	$(MODLINTLIB_DEPS)
+
+clean.lint:	$(CLEAN_LINT_DEPS)
+
+install:	$(INSTALL_DEPS)
+
+#
+#	Include common targets.
+#
+include $(UTSBASE)/intel/Makefile.targ