changeset 10433:31c72988d7f4

PSARC 2009/385 audiols driver 6875005 Desire audigy ls audio device support
author Garrett D'Amore <Garrett.Damore@Sun.COM>
date Mon, 31 Aug 2009 23:25:09 -0700
parents 72e9aabfc892
children 7a008d5633fc
files usr/src/pkgdefs/Makefile usr/src/pkgdefs/SUNWaudiols/Makefile usr/src/pkgdefs/SUNWaudiols/depend usr/src/pkgdefs/SUNWaudiols/pkginfo.tmpl usr/src/pkgdefs/SUNWaudiols/postinstall.tmpl usr/src/pkgdefs/SUNWaudiols/preremove.tmpl usr/src/pkgdefs/SUNWaudiols/prototype_com usr/src/pkgdefs/SUNWaudiols/prototype_i386 usr/src/pkgdefs/SUNWaudiols/prototype_sparc usr/src/uts/common/Makefile.files usr/src/uts/common/Makefile.rules usr/src/uts/common/io/audio/drv/audiols/audiols.c usr/src/uts/common/io/audio/drv/audiols/audiols.h usr/src/uts/intel/Makefile.intel.shared usr/src/uts/intel/audiols/Makefile usr/src/uts/sparc/Makefile.sparc.shared usr/src/uts/sparc/audiols/Makefile
diffstat 17 files changed, 2459 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/pkgdefs/Makefile	Mon Aug 31 23:03:25 2009 -0700
+++ b/usr/src/pkgdefs/Makefile	Mon Aug 31 23:25:09 2009 -0700
@@ -190,6 +190,7 @@
 	SUNWaudd  \
 	SUNWaudf  \
 	SUNWaudh  \
+	SUNWaudiols \
 	SUNWaudit \
 	SUNWatfsr  \
 	SUNWatfsu  \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/pkgdefs/SUNWaudiols/Makefile	Mon Aug 31 23:25: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/SUNWaudiols/depend	Mon Aug 31 23:25: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/SUNWaudiols/pkginfo.tmpl	Mon Aug 31 23:25: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="SUNWaudiols"
+NAME="Creative Audigy LS Audio Driver"
+ARCH="ISA"
+VERSION="ONVERS,REV=0.0.0"
+CATEGORY="system"
+DESC="SunOS audio device driver for Creative Audigy LS"
+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/SUNWaudiols/postinstall.tmpl	Mon Aug 31 23:25: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
+
+AUDIOLS_ALIASES="\
+	\"pci1102,7\"	\
+	"
+
+pkg_drvadd -i "${AUDIOLS_ALIASES}" audiols || exit 1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/pkgdefs/SUNWaudiols/preremove.tmpl	Mon Aug 31 23:25: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.
+#
+# SUNWaudiols preremove script
+
+include drv_utils
+
+pkg_drvrem audiols || exit 1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/pkgdefs/SUNWaudiols/prototype_com	Mon Aug 31 23:25: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
+#
+# SUNWaudiols
+#
+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/SUNWaudiols/prototype_i386	Mon Aug 31 23:25: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/audiols 755 root sys
+d none kernel/drv/amd64 755 root sys
+f none kernel/drv/amd64/audiols 755 root sys
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/pkgdefs/SUNWaudiols/prototype_sparc	Mon Aug 31 23:25: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
+
+#
+# Include ISA independent files (prototype_com)
+#
+!include prototype_com
+#
+# List files which are sparc specific here
+#
+# source locations relative to the prototype file
+#
+# SUNWaudiols
+#
+d none kernel/drv/sparcv9 755 root sys
+f none kernel/drv/sparcv9/audiols 755 root sys
--- a/usr/src/uts/common/Makefile.files	Mon Aug 31 23:03:25 2009 -0700
+++ b/usr/src/uts/common/Makefile.files	Mon Aug 31 23:25:09 2009 -0700
@@ -440,6 +440,8 @@
 
 AUDIOIXP_OBJS += audioixp.o
 
+AUDIOLS_OBJS += audiols.o
+
 AUDIOPCI_OBJS += audiopci.o
 
 AUDIOTS_OBJS +=	audiots.o
--- a/usr/src/uts/common/Makefile.rules	Mon Aug 31 23:03:25 2009 -0700
+++ b/usr/src/uts/common/Makefile.rules	Mon Aug 31 23:25:09 2009 -0700
@@ -576,6 +576,10 @@
 	$(COMPILE.c) -o $@ $<
 	$(CTFCONVERT_O)
 
+$(OBJS_DIR)/%.o:		$(UTSBASE)/common/io/audio/drv/audiols/%.c
+	$(COMPILE.c) -o $@ $<
+	$(CTFCONVERT_O)
+
 $(OBJS_DIR)/%.o:		$(UTSBASE)/common/io/audio/drv/audiopci/%.c
 	$(COMPILE.c) -o $@ $<
 	$(CTFCONVERT_O)
@@ -1834,6 +1838,9 @@
 $(LINTS_DIR)/%.ln:		$(UTSBASE)/common/io/audio/drv/audioixp/%.c
 	@($(LHEAD) $(LINT.c) $< $(LTAIL))
 
+$(LINTS_DIR)/%.ln:		$(UTSBASE)/common/io/audio/drv/audiols/%.c
+	@($(LHEAD) $(LINT.c) $< $(LTAIL))
+
 $(LINTS_DIR)/%.ln:		$(UTSBASE)/common/io/audio/drv/audiopci/%.c
 	@($(LHEAD) $(LINT.c) $< $(LTAIL))
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/io/audio/drv/audiols/audiols.c	Mon Aug 31 23:25:09 2009 -0700
@@ -0,0 +1,1654 @@
+/*
+ * 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.
+ */
+
+/*
+ * Purpose: Driver for the Creative Audigy LS sound card
+ */
+/*
+ *
+ * Copyright (C) 4Front Technologies 1996-2009.
+ */
+
+#include <sys/types.h>
+#include <sys/modctl.h>
+#include <sys/kmem.h>
+#include <sys/conf.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/pci.h>
+#include <sys/note.h>
+#include <sys/audio/audio_driver.h>
+#include <sys/audio/ac97.h>
+
+#include "audiols.h"
+
+static struct ddi_device_acc_attr dev_attr = {
+	DDI_DEVICE_ATTR_V0,
+	DDI_STRUCTURE_LE_ACC,
+	DDI_STRICTORDER_ACC
+};
+
+static struct ddi_device_acc_attr buf_attr = {
+	DDI_DEVICE_ATTR_V0,
+	DDI_NEVERSWAP_ACC,
+	DDI_STRICTORDER_ACC
+};
+
+static ddi_dma_attr_t dma_attr_buf = {
+	DMA_ATTR_V0,		/* version number */
+	0x00000000,		/* low DMA address range */
+	0xffffffff,		/* high DMA address range */
+	0x000fffff,		/* DMA counter (16 bits only in Audigy LS) */
+	4,			/* DMA address alignment */
+	0x3c,			/* DMA burstsizes */
+	4,			/* min effective DMA size */
+	0xffffffff,		/* max DMA xfer size */
+	0xffffffff,		/* segment boundary */
+	1,			/* s/g length */
+	4,			/* granularity of device */
+	0			/* Bus specific DMA flags */
+};
+
+static int audigyls_attach(dev_info_t *);
+static int audigyls_resume(dev_info_t *);
+static int audigyls_detach(audigyls_dev_t *);
+static int audigyls_suspend(audigyls_dev_t *);
+
+static int audigyls_open(void *, int, unsigned *, unsigned *, caddr_t *);
+static void audigyls_close(void *);
+static int audigyls_start(void *);
+static void audigyls_stop(void *);
+static int audigyls_format(void *);
+static int audigyls_channels(void *);
+static int audigyls_rate(void *);
+static uint64_t audigyls_count(void *);
+static void audigyls_sync(void *, unsigned);
+static size_t audigyls_qlen(void *);
+static void audigyls_chinfo(void *, int, unsigned *, unsigned *);
+
+
+static uint16_t audigyls_read_ac97(void *, uint8_t);
+static void audigyls_write_ac97(void *, uint8_t, uint16_t);
+static int audigyls_alloc_port(audigyls_dev_t *, int);
+static void audigyls_start_port(audigyls_port_t *);
+static void audigyls_stop_port(audigyls_port_t *);
+static void audigyls_update_port(audigyls_port_t *);
+static void audigyls_reset_port(audigyls_port_t *);
+static void audigyls_destroy(audigyls_dev_t *);
+static int audigyls_setup_intrs(audigyls_dev_t *);
+static void audigyls_hwinit(audigyls_dev_t *);
+static uint_t audigyls_intr(caddr_t, caddr_t);
+static void audigyls_configure_mixer(audigyls_dev_t *dev);
+
+static audio_engine_ops_t audigyls_engine_ops = {
+	AUDIO_ENGINE_VERSION,
+	audigyls_open,
+	audigyls_close,
+	audigyls_start,
+	audigyls_stop,
+	audigyls_count,
+	audigyls_format,
+	audigyls_channels,
+	audigyls_rate,
+	audigyls_sync,
+	audigyls_qlen,
+	audigyls_chinfo
+};
+
+/*
+ * Audigy LS uses AC'97 strictly for the recording side of things.
+ * While the chip can supposedly route output to AC'97 for playback,
+ * the PCI devices use a separate I2S DAC instead.  As a result we
+ * need to suppress controls that the AC'97 codec registers.
+ *
+ * Furthermore, even then the AC'97 codec offers inputs that we just
+ * aren't interested in.
+ */
+const char *audigyls_remove_ac97[] = {
+	AUDIO_CTRL_ID_VOLUME,
+	AUDIO_CTRL_ID_LINEOUT,
+	AUDIO_CTRL_ID_HEADPHONE,
+	AUDIO_CTRL_ID_CD,
+	AUDIO_CTRL_ID_VIDEO,
+	AUDIO_CTRL_ID_3DDEPTH,
+	AUDIO_CTRL_ID_3DENHANCE,
+	AUDIO_CTRL_ID_BEEP,
+	AUDIO_CTRL_ID_RECGAIN,
+	AUDIO_CTRL_ID_RECSRC,
+	AUDIO_CTRL_ID_LOOPBACK,
+	NULL,
+};
+
+/*
+ * AC'97 sources we don't want to expose.
+ */
+const char *audigyls_badsrcs[] = {
+	AUDIO_PORT_VIDEO,
+	AUDIO_PORT_CD,
+	AUDIO_PORT_STEREOMIX,
+	AUDIO_PORT_MONOMIX,
+	NULL,
+};
+
+static unsigned int
+read_chan(audigyls_dev_t *dev, int reg, int chn)
+{
+	uint32_t val;
+
+	mutex_enter(&dev->low_mutex);
+	/* Pointer */
+	OUTL(dev, PR, (reg << 16) | (chn & 0xffff));
+	/* Data */
+	val = INL(dev, DR);
+	mutex_exit(&dev->low_mutex);
+
+	return (val);
+}
+
+static void
+write_chan(audigyls_dev_t *dev, int reg, int chn, uint32_t value)
+{
+	mutex_enter(&dev->low_mutex);
+	/* Pointer */
+	OUTL(dev, PR, (reg << 16) | (chn & 0x7));
+	/* Data */
+	OUTL(dev, DR, value);
+	mutex_exit(&dev->low_mutex);
+}
+
+static unsigned int
+read_reg(audigyls_dev_t *dev, int reg)
+{
+	return (read_chan(dev, reg, 0));
+}
+
+static void
+write_reg(audigyls_dev_t *dev, int reg, uint32_t value)
+{
+	write_chan(dev, reg, 0, value);
+}
+
+
+static uint16_t
+audigyls_read_ac97(void *arg, uint8_t index)
+{
+	audigyls_dev_t *dev = arg;
+	uint16_t dtemp = 0;
+	int i;
+
+	mutex_enter(&dev->low_mutex);
+	OUTB(dev, AC97A, index);
+	for (i = 0; i < 10000; i++) {
+		if (INB(dev, AC97A) & 0x80)
+			break;
+	}
+	if (i == 10000) {	/* Timeout */
+		mutex_exit(&dev->low_mutex);
+		return (0xffff);
+	}
+	dtemp = INW(dev, AC97D);
+	mutex_exit(&dev->low_mutex);
+
+	return (dtemp);
+}
+
+static void
+audigyls_write_ac97(void *arg, uint8_t index, uint16_t data)
+{
+	audigyls_dev_t *dev = arg;
+	int i;
+
+	mutex_enter(&dev->low_mutex);
+	OUTB(dev, AC97A, index);
+	for (i = 0; i < 50000; i++) {
+		if (INB(dev, AC97A) & 0x80)
+			break;
+	}
+	if (i == 50000) {
+		mutex_exit(&dev->low_mutex);
+		return;
+	}
+	OUTW(dev, AC97D, data);
+	mutex_exit(&dev->low_mutex);
+}
+
+static void
+select_digital_enable(audigyls_dev_t *dev, int mode)
+{
+	/*
+	 * Set the out3/spdif combo jack format.
+	 * mode0=analog rear/center, 1=spdif
+	 */
+
+	if (mode == 0) {
+		write_reg(dev, SPC, 0x00000f00);
+	} else {
+		write_reg(dev, SPC, 0x0000000f);
+	}
+}
+
+/* only for SBLive 7.1 */
+int
+audigyls_i2c_write(audigyls_dev_t *dev, int reg, int data)
+{
+	int i, timeout, tmp;
+
+	tmp = (reg << 9 | data) << 16;	/* set the upper 16 bits */
+	/* first write the command to the data reg */
+	write_reg(dev, I2C_1, tmp);
+	for (i = 0; i < 20; i++) {
+		tmp = read_reg(dev, I2C_A) & ~0x6fe;
+		/* see audigyls.pdf for bits */
+		tmp |= 0x400 | 0x100 | 0x34;
+		write_reg(dev, I2C_A, tmp);
+		/* now wait till controller sets valid bit (0x100) to 0 */
+		timeout = 0;
+		for (;;) {
+			tmp = read_reg(dev, I2C_A);
+			if ((tmp & 0x100) == 0)
+				break;
+
+			if (timeout > 100)
+				break;
+
+			timeout++;
+		}
+
+		/* transaction aborted */
+		if (tmp & 0x200)
+			return (0);
+	}
+	return (1);
+}
+
+int
+audigyls_spi_write(audigyls_dev_t *dev, int data)
+{
+	unsigned int orig;
+	unsigned int tmp;
+	int i, valid;
+
+	tmp = read_reg(dev, SPI);
+	orig = (tmp & ~0x3ffff) | 0x30000;
+	write_reg(dev, SPI, orig | data);
+	valid = 0;
+	/* Wait for status bit to return to 0 */
+	for (i = 0; i < 1000; i++) {
+		drv_usecwait(100);
+		tmp = read_reg(dev, SPI);
+		if (!(tmp & 0x10000)) {
+			valid = 1;
+			break;
+		}
+	}
+	if (!valid)			/* Timed out */
+		return (0);
+
+	return (1);
+}
+
+static void
+audigyls_update_port(audigyls_port_t *port)
+{
+	audigyls_dev_t *dev = port->dev;
+	uint32_t	offset, n;
+
+	if (dev->suspended)
+		return;
+
+	if (port->direction == AUDIGYLS_PLAY_PORT) {
+		offset = read_chan(dev, CPFA, 0);
+	} else {
+		offset = read_chan(dev, CRFA, 2);
+	}
+
+
+	/* get the offset, and switch to frames */
+	offset /= (2 * sizeof (uint16_t));
+
+	if (offset >= port->offset) {
+		n = offset - port->offset;
+	} else {
+		n = offset + (port->buf_frames - port->offset);
+	}
+	port->offset = offset;
+	port->count += n;
+}
+
+static void
+check_play_intr(audigyls_dev_t *dev)
+{
+	audigyls_port_t *port = dev->port[AUDIGYLS_PLAY_PORT];
+
+	if (!port->started)
+		return;
+	audio_engine_consume(port->engine);
+}
+
+static void
+check_rec_intr(audigyls_dev_t *dev)
+{
+	audigyls_port_t *port = dev->port[AUDIGYLS_REC_PORT];
+
+	if (!port->started)
+		return;
+	audio_engine_produce(port->engine);
+}
+
+static uint_t
+audigyls_intr(caddr_t argp, caddr_t nocare)
+{
+	audigyls_dev_t	*dev = (void *)argp;
+	uint32_t	status;
+
+	_NOTE(ARGUNUSED(nocare));
+	status = INL(dev, IPR);
+
+	if (dev->suspended) {
+		OUTL(dev, IPR, status);		/* Acknowledge */
+		return (DDI_INTR_UNCLAIMED);
+	}
+
+	if (!(status & (INTR_IT1 | INTR_PCI))) {
+		/* No audio interrupts pending */
+		OUTL(dev, IPR, status);		/* Acknowledge */
+		return (DDI_INTR_UNCLAIMED);
+	}
+
+	check_play_intr(dev);
+	check_rec_intr(dev);
+
+	if (dev->ksp) {
+		AUDIGYLS_KIOP(dev)->intrs[KSTAT_INTR_HARD]++;
+	}
+
+	OUTL(dev, IPR, status);		/* Acknowledge */
+	return (DDI_INTR_CLAIMED);
+}
+
+/*
+ * Audio routines
+ */
+
+int
+audigyls_open(void *arg, int flag,
+    unsigned *fragfrp, unsigned *nfragsp, caddr_t *bufp)
+{
+	audigyls_port_t	 *port = arg;
+	audigyls_dev_t	 *dev = port->dev;
+
+	_NOTE(ARGUNUSED(flag));
+
+	mutex_enter(&dev->mutex);
+
+	port->started = B_FALSE;
+	port->count = 0;
+	*fragfrp = port->fragfr;
+	*nfragsp = AUDIGYLS_NUM_FRAGS;
+	*bufp = port->buf_kaddr;
+	audigyls_reset_port(port);
+	mutex_exit(&dev->mutex);
+
+	return (0);
+}
+
+void
+audigyls_close(void *arg)
+{
+	audigyls_port_t *port = arg;
+	audigyls_dev_t	 *dev = port->dev;
+
+	mutex_enter(&dev->mutex);
+	audigyls_stop_port(port);
+	port->started = B_FALSE;
+	mutex_exit(&dev->mutex);
+}
+
+int
+audigyls_start(void *arg)
+{
+	audigyls_port_t *port = arg;
+	audigyls_dev_t	*dev = port->dev;
+
+	mutex_enter(&dev->mutex);
+	if (!port->started) {
+		audigyls_start_port(port);
+		port->started = B_TRUE;
+	}
+	mutex_exit(&dev->mutex);
+	return (0);
+}
+
+void
+audigyls_stop(void *arg)
+{
+	audigyls_port_t	*port = arg;
+	audigyls_dev_t	*dev = port->dev;
+
+	mutex_enter(&dev->mutex);
+	if (port->started) {
+		audigyls_stop_port(port);
+		port->started = B_FALSE;
+	}
+	mutex_exit(&dev->mutex);
+}
+
+int
+audigyls_format(void *arg)
+{
+	_NOTE(ARGUNUSED(arg));
+
+	return (AUDIO_FORMAT_S16_LE);
+}
+
+int
+audigyls_channels(void *arg)
+{
+	audigyls_port_t	*port = arg;
+
+	return (port->nchan);
+}
+
+int
+audigyls_rate(void *arg)
+{
+	_NOTE(ARGUNUSED(arg));
+
+	return (48000);
+}
+
+void
+audigyls_sync(void *arg, unsigned nframes)
+{
+	audigyls_port_t *port = arg;
+	_NOTE(ARGUNUSED(nframes));
+
+	(void) ddi_dma_sync(port->buf_dmah, 0, 0, port->syncdir);
+}
+
+size_t
+audigyls_qlen(void *arg)
+{
+	_NOTE(ARGUNUSED(arg));
+	return (0);
+}
+
+uint64_t
+audigyls_count(void *arg)
+{
+	audigyls_port_t	*port = arg;
+	audigyls_dev_t	*dev = port->dev;
+	uint64_t	count;
+
+	mutex_enter(&dev->mutex);
+	if (!dev->suspended)
+		audigyls_update_port(port);
+	count = port->count;
+	mutex_exit(&dev->mutex);
+	return (count);
+}
+
+static void
+audigyls_chinfo(void *arg, int chan, unsigned *offset, unsigned *incr)
+{
+	audigyls_port_t *port = arg;
+
+	if (port->direction == AUDIGYLS_PLAY_PORT) {
+		*offset = (port->buf_frames * 2 * (chan / 2)) + (chan % 2);
+		*incr = 2;
+	} else {
+		*offset = chan;
+		*incr = 2;
+	}
+}
+
+/* private implementation bits */
+
+void
+audigyls_start_port(audigyls_port_t *port)
+{
+	audigyls_dev_t	*dev = port->dev;
+	uint32_t tmp;
+
+	ASSERT(mutex_owned(&dev->mutex));
+
+	if (dev->suspended || port->active)
+		return;
+
+	port->active = B_TRUE;
+	port->offset = 0;
+	dev->nactive++;
+
+	if (dev->nactive == 1) {
+		write_reg(dev, IT, dev->timer);
+		OUTL(dev, IER, INL(dev, IER) | INTR_IT1);
+	}
+
+	switch (port->direction) {
+	case AUDIGYLS_PLAY_PORT:
+		tmp = read_reg(dev, SA);
+		tmp |= SA_SPA(0);
+		tmp |= SA_SPA(1);
+		tmp |= SA_SPA(3);
+		write_reg(dev, SA, tmp);
+		break;
+
+	case AUDIGYLS_REC_PORT:
+		tmp = read_reg(dev, SA);
+		tmp |= SA_SRA(2);
+		write_reg(dev, SA, tmp);
+		break;
+	}
+
+}
+
+void
+audigyls_stop_port(audigyls_port_t *port)
+{
+	audigyls_dev_t	*dev = port->dev;
+	uint32_t tmp;
+
+	if (dev->suspended || !port->active)
+		return;
+
+	port->active = B_FALSE;
+	dev->nactive--;
+
+	switch (port->direction) {
+	case AUDIGYLS_PLAY_PORT:
+		tmp = read_reg(dev, SA);
+		tmp &= ~SA_SPA(0);
+		tmp &= ~SA_SPA(1);
+		tmp &= ~SA_SPA(3);
+		write_reg(dev, SA, tmp);
+		break;
+
+	case AUDIGYLS_REC_PORT:
+		tmp = read_reg(dev, SA);
+		tmp &= ~SA_SRA(2);
+		write_reg(dev, SA, tmp);
+		break;
+	}
+
+	if (dev->nactive == 0) {
+		OUTL(dev, IER, INL(dev, IER) & ~INTR_IT1);
+	}
+}
+
+void
+audigyls_reset_port(audigyls_port_t *port)
+{
+	audigyls_dev_t	*dev = port->dev;
+
+	ASSERT(mutex_owned(&dev->mutex));
+
+	if (dev->suspended)
+		return;
+
+	switch (port->direction) {
+	case AUDIGYLS_PLAY_PORT:
+		write_chan(dev, PTCA, 0, 0);
+		write_chan(dev, CPFA, 0, 0);
+		write_chan(dev, CPCAV, 0, 0);
+		write_chan(dev, PTCA, 1, 0);
+		write_chan(dev, CPFA, 1, 0);
+		write_chan(dev, CPCAV, 1, 0);
+		write_chan(dev, PTCA, 3, 0);
+		write_chan(dev, CPFA, 3, 0);
+		write_chan(dev, CPCAV, 3, 0);
+		break;
+
+	case AUDIGYLS_REC_PORT:
+		write_chan(dev, CRFA, 2, 0);
+		write_chan(dev, CRCAV, 2, 0);
+		break;
+	}
+}
+
+int
+audigyls_alloc_port(audigyls_dev_t *dev, int num)
+{
+	audigyls_port_t		*port;
+	size_t			len;
+	ddi_dma_cookie_t	cookie;
+	uint_t			count;
+	int			dir;
+	unsigned		caps;
+	audio_dev_t		*adev;
+
+	adev = dev->adev;
+	port = kmem_zalloc(sizeof (*port), KM_SLEEP);
+	dev->port[num] = port;
+	port->dev = dev;
+	port->started = B_FALSE;
+	port->direction = num;
+
+	switch (num) {
+	case AUDIGYLS_REC_PORT:
+		port->syncdir = DDI_DMA_SYNC_FORKERNEL;
+		caps = ENGINE_INPUT_CAP;
+		dir = DDI_DMA_READ;
+		port->nchan = 2;
+		break;
+	case AUDIGYLS_PLAY_PORT:
+		port->syncdir = DDI_DMA_SYNC_FORDEV;
+		caps = ENGINE_OUTPUT_CAP;
+		dir = DDI_DMA_WRITE;
+		port->nchan = 6;
+		break;
+	default:
+		return (DDI_FAILURE);
+	}
+
+	/* figure out fragment configuration */
+	port->fragfr = 48000 / dev->intrs;
+	/* we want to make sure that we are aligning on reasonable chunks */
+	port->fragfr = (port->fragfr + 63) & ~(63);
+
+	/* 16 bit frames */
+	port->fragsz = port->fragfr * 2 * port->nchan;
+	port->buf_size = port->fragsz * AUDIGYLS_NUM_FRAGS;
+	port->buf_frames = port->fragfr * AUDIGYLS_NUM_FRAGS;
+
+	/* Alloc buffers */
+	if (ddi_dma_alloc_handle(dev->dip, &dma_attr_buf, DDI_DMA_SLEEP, NULL,
+	    &port->buf_dmah) != DDI_SUCCESS) {
+		audio_dev_warn(adev, "failed to allocate BUF handle");
+		return (DDI_FAILURE);
+	}
+
+	if (ddi_dma_mem_alloc(port->buf_dmah, port->buf_size,
+	    &buf_attr, DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL,
+	    &port->buf_kaddr, &len, &port->buf_acch) != DDI_SUCCESS) {
+		audio_dev_warn(adev, "failed to allocate BUF memory");
+		return (DDI_FAILURE);
+	}
+
+	if (ddi_dma_addr_bind_handle(port->buf_dmah, NULL, port->buf_kaddr,
+	    len, DDI_DMA_CONSISTENT | dir, DDI_DMA_SLEEP, NULL, &cookie,
+	    &count) != DDI_SUCCESS) {
+		audio_dev_warn(adev, "failed binding BUF DMA handle");
+		return (DDI_FAILURE);
+	}
+	port->buf_paddr = cookie.dmac_address;
+
+	port->engine = audio_engine_alloc(&audigyls_engine_ops, caps);
+	if (port->engine == NULL) {
+		audio_dev_warn(adev, "audio_engine_alloc failed");
+		return (DDI_FAILURE);
+	}
+
+	audio_engine_set_private(port->engine, port);
+	audio_dev_add_engine(adev, port->engine);
+
+	return (DDI_SUCCESS);
+}
+
+int
+audigyls_setup_intrs(audigyls_dev_t *dev)
+{
+	uint_t			ipri;
+	int			actual;
+	int			rv;
+	ddi_intr_handle_t	ih[1];
+
+	rv = ddi_intr_alloc(dev->dip, ih, DDI_INTR_TYPE_FIXED,
+	    0, 1, &actual, DDI_INTR_ALLOC_STRICT);
+	if ((rv != DDI_SUCCESS) || (actual != 1)) {
+		audio_dev_warn(dev->adev,
+		    "Can't alloc interrupt handle (rv %d actual %d)",
+		    rv, actual);
+		return (DDI_FAILURE);
+	}
+
+	if (ddi_intr_get_pri(ih[0], &ipri) != DDI_SUCCESS) {
+		audio_dev_warn(dev->adev, "Can't get interrupt priority");
+		(void) ddi_intr_free(ih[0]);
+		return (DDI_FAILURE);
+	}
+
+	if (ddi_intr_add_handler(ih[0], audigyls_intr, dev, NULL) !=
+	    DDI_SUCCESS) {
+		audio_dev_warn(dev->adev, "Can't add interrupt handler");
+		(void) ddi_intr_free(ih[0]);
+		return (DDI_FAILURE);
+	}
+
+	dev->ih = ih[0];
+	mutex_init(&dev->mutex, NULL, MUTEX_DRIVER, DDI_INTR_PRI(ipri));
+	mutex_init(&dev->low_mutex, NULL, MUTEX_DRIVER, DDI_INTR_PRI(ipri));
+	return (DDI_SUCCESS);
+}
+
+void
+audigyls_destroy(audigyls_dev_t *dev)
+{
+	if (dev->ih != NULL) {
+		(void) ddi_intr_disable(dev->ih);
+		(void) ddi_intr_remove_handler(dev->ih);
+		(void) ddi_intr_free(dev->ih);
+		mutex_destroy(&dev->mutex);
+		mutex_destroy(&dev->low_mutex);
+	}
+
+	if (dev->ksp) {
+		kstat_delete(dev->ksp);
+	}
+
+	for (int i = 0; i < AUDIGYLS_NUM_PORT; i++) {
+		audigyls_port_t *port = dev->port[i];
+		if (!port)
+			continue;
+		if (port->engine) {
+			audio_dev_remove_engine(dev->adev, port->engine);
+			audio_engine_free(port->engine);
+		}
+		if (port->buf_paddr) {
+			(void) ddi_dma_unbind_handle(port->buf_dmah);
+		}
+		if (port->buf_acch) {
+			ddi_dma_mem_free(&port->buf_acch);
+		}
+		if (port->buf_dmah) {
+			ddi_dma_free_handle(&port->buf_dmah);
+		}
+		kmem_free(port, sizeof (*port));
+	}
+
+	if (dev->ac97 != NULL) {
+		ac97_free(dev->ac97);
+	}
+	if (dev->adev != NULL) {
+		audio_dev_free(dev->adev);
+	}
+	if (dev->regsh != NULL) {
+		ddi_regs_map_free(&dev->regsh);
+	}
+	if (dev->pcih != NULL) {
+		pci_config_teardown(&dev->pcih);
+	}
+	kmem_free(dev, sizeof (*dev));
+}
+
+void
+audigyls_hwinit(audigyls_dev_t *dev)
+{
+	static unsigned int spi_dac[] = {
+		0x00ff, 0x02ff, 0x0400, 0x520, 0x0620, 0x08ff, 0x0aff, 0x0cff,
+		0x0eff, 0x10ff, 0x1200, 0x1400, 0x1800, 0x1aff, 0x1cff,
+		0x1e00, 0x0530, 0x0602, 0x0622, 0x1400,
+	};
+
+	uint32_t	tmp;
+	int		i, tries;
+	uint32_t	paddr;
+	uint32_t	chunksz;
+	audigyls_port_t	*port;
+
+
+	/* Set the orange jack to be analog out or S/PDIF */
+	select_digital_enable(dev, dev->digital_enable);
+
+	/*
+	 * In P17, there's 8 GPIO pins.
+	 * GPIO register: 0x00XXYYZZ
+	 * XX: Configure GPIO to be either GPI (0) or GPO (1).
+	 * YY: GPO values, applicable if the pin is configure to be GPO.
+	 * ZZ: GPI values, applicable if the pin is configure to be GPI.
+	 *
+	 * in SB570, pin 0-4 and 6 is used as GPO and pin 5 and 7 is
+	 * used as GPI.
+	 *
+	 * GPO0:
+	 * 1 ==> Analog output
+	 * 0 ==> Digital output
+	 * GPO1:
+	 * 1 ==> Enable output on card
+	 * 0 ==> Disable output on card
+	 * GPO2:
+	 * 1 ==> Enable Mic Bias and Mic Path
+	 * 0 ==> Disable Mic Bias and Mic Path
+	 * GPO3:
+	 * 1 ==> Disable SPDIF-IO output
+	 * 0 ==> Enable SPDIF-IO output
+	 * GPO4 and GPO6:
+	 * DAC sampling rate selection:
+	 * Not applicable to SB570 since DAC is controlled through SPI
+	 * GPI5:
+	 * 1 ==> Front Panel is not connected
+	 * 0 ==> Front Panel is connected
+	 * GPI7:
+	 * 1 ==> Front Panel Headphone is not connected
+	 * 0 ==> Front Panel Headphone is connected
+	 */
+	if (dev->ac97)
+		OUTL(dev, GPIO, 0x005f03a3);
+	else {
+		/* for SBLive 7.1 */
+		OUTL(dev, GPIO, 0x005f4301);
+
+		audigyls_i2c_write(dev, 0x15, 0x2);
+		tries = 0;
+	again:
+		for (i = 0; i < sizeof (spi_dac); i++) {
+			if (!audigyls_spi_write(dev, spi_dac[i]) &&
+			    tries < 100) {
+				tries++;
+				goto again;
+			}
+		}
+	}
+
+	OUTL(dev, IER, INTR_PCI);
+	OUTL(dev, HC, 0x00000009);	/* Enable audio, use 48 kHz */
+
+	tmp = read_chan(dev, SRCTL, 0);
+	if (dev->ac97)
+		tmp |= 0xf0c81000;	/* Record src0/src1 from ac97 */
+	else
+		tmp |= 0x50c81000;	/* Record src0/src1 from I2SIN */
+	tmp &= ~0x0303c00f;		/* Set sample rates to 48 kHz */
+	write_chan(dev, SRCTL, 0, tmp);
+
+	write_reg(dev, HMIXMAP_I2S, 0x76543210);	/* Default out route */
+	write_reg(dev, AUDCTL, 0x0f0f003f);	/* Enable all outputs */
+
+	/* All audio stopped! */
+	write_reg(dev, SA, 0);
+
+	for (i = 0; i < 4; i++) {
+		/*
+		 * Reset DMA pointers and counters.  Note that we do
+		 * not use scatter/gather.
+		 */
+		write_chan(dev, PTBA, i, 0);
+		write_chan(dev, PTBS, i, 0);
+		write_chan(dev, PTCA, i, 0);
+
+		write_chan(dev, CPFA, i, 0);
+		write_chan(dev, PFEA, i, 0);
+		write_chan(dev, CPCAV, i, 0);
+
+		write_chan(dev, CRFA, i, 0);
+		write_chan(dev, CRCAV, i, 0);
+	}
+
+	/*
+	 * The 5.1 play port made up channels 0, 1, and 3.  The record
+	 * port is channel 2.
+	 */
+	port = dev->port[AUDIGYLS_PLAY_PORT];
+	paddr = port->buf_paddr;
+	chunksz = port->buf_frames * 4;
+	write_chan(dev, PFBA, 0, paddr);
+	write_chan(dev, PFBS, 0, chunksz << 16);
+	paddr += chunksz;
+	write_chan(dev, PFBA, 1, paddr);
+	write_chan(dev, PFBS, 1, chunksz << 16);
+	paddr += chunksz;
+	write_chan(dev, PFBA, 3, paddr);
+	write_chan(dev, PFBS, 3, chunksz << 16);
+
+	/* Record */
+	port = dev->port[AUDIGYLS_REC_PORT];
+	paddr = port->buf_paddr;
+	chunksz = port->buf_frames * 4;
+	write_chan(dev, RFBA, 2, paddr);
+	write_chan(dev, RFBS, 2, chunksz << 16);
+
+	/* Set sample rates to 48 kHz. */
+	tmp = read_chan(dev, SRCTL, 0) & ~0x0303c00f;
+	write_chan(dev, SRCTL, 0, tmp);
+
+	write_reg(dev, SCS0, 0x02108004);	/* Audio */
+	write_reg(dev, SCS1, 0x02108004);	/* Audio */
+	write_reg(dev, SCS2, 0x02108004);	/* Audio */
+	write_reg(dev, SCS3, 0x02108004);	/* Audio */
+}
+
+#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)
+#define	MONVOL	(MONCTL | AUDIO_CTRL_FLAG_MONVOL)
+
+#define	MASK(nbits)	((1 << (nbits)) - 1)
+#define	SCALE(val, nbits)	\
+	((uint8_t)((((val) * MASK(nbits)) / 100)) << (8 - (nbits)))
+
+static uint32_t
+audigyls_stereo_scale(uint32_t value, uint8_t bits)
+{
+	uint8_t			left, right;
+	uint32_t		val;
+
+	left = (value >> 8) & 0xff;
+	right = value & 0xff;
+
+	val = (((left * ((1 << bits) - 1) / 100) << 8) |
+	    (right * ((1 << bits) - 1) / 100));
+	return (val);
+}
+
+static void
+audigyls_configure_mixer(audigyls_dev_t *dev)
+{
+	unsigned int	r, v1, v2;
+
+	ASSERT(mutex_owned(&dev->mutex));
+
+	/* output items */
+	/* front */
+	r = 0xffff - audigyls_stereo_scale(dev->controls[CTL_FRONT].val, 8);
+	r = (r << 16) | r;
+	write_chan(dev, MIXVOL_I2S, 0, r);
+
+	/* surround */
+	r = 0xffff - audigyls_stereo_scale(dev->controls[CTL_SURROUND].val, 8);
+	r = (r << 16) | r;
+	write_chan(dev, MIXVOL_I2S, 3, r);
+
+	/* center/lfe */
+	v1 = 255 - SCALE(dev->controls[CTL_CENTER].val, 8);
+	v2 = 255 - SCALE(dev->controls[CTL_LFE].val, 8);
+	r = (v1 << 8) | v2;
+	r = (r << 16) | r;
+	write_chan(dev, MIXVOL_I2S, 1, r);
+
+	/* spread */
+	r = dev->controls[CTL_SPREAD].val ? 0x10101010 : 0x76543210;
+	write_reg(dev, HMIXMAP_I2S, r);
+
+	/* input items */
+
+	/* recgain */
+	v1 = dev->controls[CTL_RECORDVOL].val;
+	if (dev->ac97_recgain && !dev->controls[CTL_LOOP].val) {
+		/*
+		 * For AC'97, we use the AC'97 record gain, unless we are
+		 * in loopback.
+		 */
+		ac97_control_set(dev->ac97_recgain, v1);
+		write_reg(dev, P17RECVOLL, 0x30303030);
+		write_reg(dev, P17RECVOLH, 0x30303030);
+	} else {
+		/*
+		 * Otherwise we set the P17 gain.
+		 */
+		r = 0xffff - audigyls_stereo_scale(v1, 8);
+		r = r << 16 | r;
+		write_reg(dev, P17RECVOLL, r);
+		write_reg(dev, P17RECVOLH, r);
+	}
+
+	/* monitor gain */
+	if (dev->ac97) {
+		/* AC'97 monitor gain is done by the AC'97 codec */
+		write_chan(dev, SRCTL, 1, 0x30303030);
+		write_reg(dev, SMIXMAP_I2S, 0x10101076);
+	} else {
+		/* For non-AC'97 devices, just a single master monitor gain */
+		r = 255 - SCALE(dev->controls[CTL_MONGAIN].val, 8);
+		write_chan(dev, SRCTL, 1, 0xffff0000 | r << 8 | r);
+		if (r != 0xff) {
+			write_reg(dev, SMIXMAP_I2S, 0x10101076);
+		} else {
+			write_reg(dev, SMIXMAP_I2S, 0x10101010);
+		}
+	}
+
+	/* record source */
+	if (dev->ac97_recsrc != NULL) {
+		ac97_control_set(dev->ac97_recsrc,
+		    dev->controls[CTL_RECSRC].val);
+		v1 = RECSEL_AC97;	/* Audigy LS */
+	} else {
+		switch (dev->controls[CTL_RECSRC].val) {
+		case 1:
+			audigyls_i2c_write(dev, 0x15, 0x2);   /* Mic */
+			OUTL(dev, GPIO, INL(dev, GPIO) | 0x400);
+			break;
+
+		case 2:
+			audigyls_i2c_write(dev, 0x15, 0x4);   /* Line */
+			OUTL(dev, GPIO, INL(dev, GPIO) & ~0x400);
+			break;
+		}
+		v1 = RECSEL_I2SIN;	/* SB 7.1 value */
+	}
+
+	/* If loopback, record what you hear instead */
+
+	if (dev->controls[CTL_LOOP].val) {
+		r = 0;
+		v1 = RECSEL_I2SOUT;
+		r |= (v1 << 28) | (v1 << 24) | (v1 << 20) | (v1 << 16) | v2;
+	} else {
+		/*
+		 * You'd think this would be the same as the logic
+		 * above, but experience shows that what you need for
+		 * loopback is different.  This whole thing looks
+		 * particularly fishy to me.  I suspect someone has
+		 * made a mistake somewhere.  But I can't seem to
+		 * figure out where it lies.
+		 */
+		r = 0xe4;
+		for (int i = 0; i < 4; i++)
+			r |= v1 << (16 + i * 3);	/* Select input */
+	}
+
+	write_reg(dev, P17RECSEL, r);
+}
+
+static int
+audigyls_set_control(void *arg, uint64_t val)
+{
+	audigyls_ctrl_t	*pc = arg;
+	audigyls_dev_t	*dev = pc->dev;
+
+	switch (pc->num) {
+
+	case CTL_FRONT:
+	case CTL_SURROUND:
+	case CTL_RECORDVOL:
+		if (((val & 0xff) > 100) ||
+		    (((val & 0xff00) >> 8) > 100) ||
+		    ((val & ~0xffff) != 0)) {
+			return (EINVAL);
+		}
+		break;
+
+	case CTL_CENTER:
+	case CTL_LFE:
+	case CTL_MONGAIN:
+		if (val > 100) {
+			return (EINVAL);
+		}
+		break;
+
+	case CTL_RECSRC:
+		if (((1U << val) & (dev->recmask)) == 0) {
+			return (EINVAL);
+		}
+		break;
+
+	case CTL_SPREAD:
+	case CTL_LOOP:
+		switch (val) {
+		case 0:
+		case 1:
+			break;
+		default:
+			return (EINVAL);
+		}
+	}
+
+	mutex_enter(&dev->mutex);
+	pc->val = val;
+	if (!dev->suspended) {
+		audigyls_configure_mixer(dev);
+	}
+	mutex_exit(&dev->mutex);
+
+	return (0);
+}
+
+static int
+audigyls_get_control(void *arg, uint64_t *val)
+{
+	audigyls_ctrl_t	*pc = arg;
+	audigyls_dev_t	*dev = pc->dev;
+
+	mutex_enter(&dev->mutex);
+	*val = pc->val;
+	mutex_exit(&dev->mutex);
+	return (0);
+}
+
+static void
+audigyls_alloc_ctrl(audigyls_dev_t *dev, uint32_t num, uint64_t val)
+{
+	audio_ctrl_desc_t	desc;
+	audigyls_ctrl_t		*pc;
+
+	bzero(&desc, sizeof (desc));
+
+	pc = &dev->controls[num];
+	pc->num = num;
+	pc->dev = dev;
+
+
+	switch (num) {
+	case CTL_FRONT:
+		desc.acd_name = AUDIO_CTRL_ID_FRONT;
+		desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
+		desc.acd_minvalue = 0;
+		desc.acd_maxvalue = 100;
+		desc.acd_flags = MAINVOL;
+		break;
+
+	case CTL_SURROUND:
+		desc.acd_name = AUDIO_CTRL_ID_SURROUND;
+		desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
+		desc.acd_minvalue = 0;
+		desc.acd_maxvalue = 100;
+		desc.acd_flags = MAINVOL;
+		break;
+
+	case CTL_CENTER:
+		desc.acd_name = AUDIO_CTRL_ID_CENTER;
+		desc.acd_type = AUDIO_CTRL_TYPE_MONO;
+		desc.acd_minvalue = 0;
+		desc.acd_maxvalue = 100;
+		desc.acd_flags = MAINVOL;
+		break;
+
+	case CTL_LFE:
+		desc.acd_name = AUDIO_CTRL_ID_LFE;
+		desc.acd_type = AUDIO_CTRL_TYPE_MONO;
+		desc.acd_minvalue = 0;
+		desc.acd_maxvalue = 100;
+		desc.acd_flags = MAINVOL;
+		break;
+
+	case CTL_RECORDVOL:
+		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 = RECVOL;
+		break;
+
+	case CTL_RECSRC:
+		desc.acd_name = AUDIO_CTRL_ID_RECSRC;
+		desc.acd_type = AUDIO_CTRL_TYPE_ENUM;
+		desc.acd_flags = RECCTL;
+
+		/*
+		 * For AC'97 devices, we want to expose the reasonable
+		 * AC'97 input sources, but suppress the stereomix,
+		 * because we use loopback instead.
+		 */
+		if (dev->ac97_recsrc) {
+			int i, j;
+			const char *n;
+			const audio_ctrl_desc_t *adp;
+
+			adp = ac97_control_desc(dev->ac97_recsrc);
+			for (i = 0; i < 64; i++) {
+				n = adp->acd_enum[i];
+
+				if (((adp->acd_minvalue & (1 << i)) == 0) ||
+				    (n == NULL)) {
+					continue;
+				}
+				for (j = 0; audigyls_badsrcs[j]; j++) {
+					if (strcmp(n, audigyls_badsrcs[j])
+					    == 0) {
+						n = NULL;
+						break;
+					}
+				}
+				if (n) {
+					desc.acd_enum[i] = n;
+					dev->recmask |= (1 << i);
+				}
+			}
+			desc.acd_minvalue = desc.acd_maxvalue = dev->recmask;
+		} else {
+			dev->recmask = 3;
+			desc.acd_minvalue = 3;
+			desc.acd_maxvalue = 3;
+			desc.acd_enum[0] = AUDIO_PORT_MIC;
+			desc.acd_enum[1] = AUDIO_PORT_LINEIN;
+		}
+		break;
+
+	case CTL_MONGAIN:
+		ASSERT(!dev->ac97);
+		desc.acd_name = AUDIO_CTRL_ID_MONGAIN;
+		desc.acd_type = AUDIO_CTRL_TYPE_MONO;
+		desc.acd_minvalue = 0;
+		desc.acd_maxvalue = 100;
+		desc.acd_flags = MONVOL;
+		break;
+
+	case CTL_SPREAD:
+		desc.acd_name = AUDIO_CTRL_ID_SPREAD;
+		desc.acd_type = AUDIO_CTRL_TYPE_BOOLEAN;
+		desc.acd_minvalue = 0;
+		desc.acd_maxvalue = 1;
+		desc.acd_flags = PLAYCTL;
+		break;
+
+	case CTL_LOOP:
+		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;
+		break;
+	}
+
+	pc->val = val;
+	pc->ctrl = audio_dev_add_control(dev->adev, &desc,
+	    audigyls_get_control, audigyls_set_control, pc);
+}
+
+static int
+audigyls_add_controls(audigyls_dev_t *dev)
+{
+	(void) audio_dev_add_soft_volume(dev->adev);
+
+	audigyls_alloc_ctrl(dev, CTL_FRONT, 75 | (75 << 8));
+	audigyls_alloc_ctrl(dev, CTL_SURROUND, 75 | (75 << 8));
+	audigyls_alloc_ctrl(dev, CTL_CENTER, 75);
+	audigyls_alloc_ctrl(dev, CTL_LFE, 75);
+	audigyls_alloc_ctrl(dev, CTL_RECORDVOL, 75 | (75 << 8));
+	audigyls_alloc_ctrl(dev, CTL_RECSRC, 1);
+	audigyls_alloc_ctrl(dev, CTL_SPREAD, 0);
+	audigyls_alloc_ctrl(dev, CTL_LOOP, 0);
+	if (!dev->ac97) {
+		audigyls_alloc_ctrl(dev, CTL_MONGAIN, 0);
+	}
+
+	return (DDI_SUCCESS);
+}
+
+int
+audigyls_attach(dev_info_t *dip)
+{
+	uint16_t	pci_command, vendor, device;
+	uint32_t	subdevice;
+	audigyls_dev_t	*dev;
+	ddi_acc_handle_t pcih;
+	const char	*name, *version;
+	boolean_t	ac97 = B_FALSE;
+
+	dev = kmem_zalloc(sizeof (*dev), KM_SLEEP);
+	dev->dip = dip;
+	ddi_set_driver_private(dip, dev);
+
+	if ((dev->adev = audio_dev_alloc(dip, 0)) == NULL) {
+		cmn_err(CE_WARN, "audio_dev_alloc failed");
+		goto error;
+	}
+
+	if (pci_config_setup(dip, &pcih) != DDI_SUCCESS) {
+		audio_dev_warn(dev->adev, "pci_config_setup failed");
+		goto error;
+	}
+	dev->pcih = pcih;
+
+	vendor = pci_config_get16(pcih, PCI_CONF_VENID);
+	device = pci_config_get16(pcih, PCI_CONF_DEVID);
+	subdevice = pci_config_get16(pcih, PCI_CONF_SUBVENID);
+	subdevice <<= 16;
+	subdevice |= pci_config_get16(pcih, PCI_CONF_SUBSYSID);
+	if (vendor != PCI_VENDOR_ID_CREATIVE ||
+	    device != PCI_DEVICE_ID_CREATIVE_AUDIGYLS) {
+		audio_dev_warn(dev->adev, "Hardware not recognized "
+		    "(vendor=%x, dev=%x)", vendor, device);
+		goto error;
+	}
+
+	pci_command = pci_config_get16(pcih, PCI_CONF_COMM);
+	pci_command |= PCI_COMM_ME | PCI_COMM_IO;
+	pci_config_put16(pcih, PCI_CONF_COMM, pci_command);
+
+	if ((ddi_regs_map_setup(dip, 1, &dev->base, 0, 0, &dev_attr,
+	    &dev->regsh)) != DDI_SUCCESS) {
+		audio_dev_warn(dev->adev, "failed to map registers");
+		goto error;
+	}
+
+	/* Function of the orange jack: 0=analog, 1=digital */
+	dev->digital_enable = ddi_prop_get_int(DDI_DEV_T_ANY, dev->dip,
+	    DDI_PROP_DONTPASS, "digital-enable", 0);
+
+	if (audigyls_setup_intrs(dev) != DDI_SUCCESS)
+		goto error;
+
+	dev->intrs = ddi_prop_get_int(DDI_DEV_T_ANY, dev->dip,
+	    DDI_PROP_DONTPASS, "interrupt-rate", AUDIGYLS_INTRS);
+
+	/* make sure the values are good */
+	if (dev->intrs < AUDIGYLS_MIN_INTRS) {
+		audio_dev_warn(dev->adev,
+		    "interrupt-rate too low, %d, reset to %d",
+		    dev->intrs, AUDIGYLS_INTRS);
+		dev->intrs = AUDIGYLS_INTRS;
+	} else if (dev->intrs > AUDIGYLS_MAX_INTRS) {
+		audio_dev_warn(dev->adev,
+		    "interrupt-rate too high, %d, reset to %d",
+		    dev->intrs, AUDIGYLS_INTRS);
+		dev->intrs = AUDIGYLS_INTRS;
+	}
+
+	dev->timer = (192000 / dev->intrs) << 16;
+
+	switch (subdevice) {
+	case 0x11021001:	/* SB0310 */
+	case 0x11021002:	/* SB0310 */
+	case 0x11021005:	/* SB0310b */
+		name = "Creative Audigy LS";
+		version = "SB0310";	/* could also be SB0312 */
+		ac97 = B_TRUE;
+		break;
+	case 0x11021006:
+		name = "Creative Sound Blaster Live! 24 bit";
+		version = "SB0410";
+		break;
+	case 0x11021007:	/* Dell OEM version */
+		name = "Creative Sound Blaster Live! 24 bit";
+		version = "SB0413";
+		break;
+	case 0x1102100a:
+		name = "Creative Audigy SE";
+		version = "SB0570";
+		break;
+	case 0x11021011:
+		name = "Creative Audigy SE OEM";
+		version = "SB0570a";
+		break;
+	case 0x11021012:
+		name = "Creative X-Fi Extreme Audio";
+		version = "SB0790";
+		break;
+	case 0x14621009:
+		name = "MSI K8N Diamond MB";
+		version = "SB0438";
+		break;
+	case 0x12973038:
+		name = "Shuttle XPC SD31P";
+		version = "SD31P";
+		break;
+	case 0x12973041:
+		name = "Shuttle XPC SD11G5";
+		version = "SD11G5";
+		break;
+	default:
+		name = "Creative Audigy LS";
+		version = NULL;
+		break;
+	}
+
+	audio_dev_set_description(dev->adev, name);
+	if (version)
+		audio_dev_set_version(dev->adev, version);
+
+	if (ac97) {
+		ac97_ctrl_t *ctrl;
+
+		/* Original Audigy LS revision (AC97 based) */
+		dev->ac97 = ac97_allocate(dev->adev, dip,
+		    audigyls_read_ac97, audigyls_write_ac97, dev);
+		if (dev->ac97 == NULL) {
+			audio_dev_warn(dev->adev,
+			    "failed to allocate ac97 handle");
+			goto error;
+		}
+
+		ac97_probe_controls(dev->ac97);
+
+		/* remove the AC'97 controls we don't want to expose */
+		for (int i = 0; audigyls_remove_ac97[i]; i++) {
+			ctrl = ac97_control_find(dev->ac97,
+			    audigyls_remove_ac97[i]);
+			if (ctrl != NULL) {
+				ac97_control_unregister(ctrl);
+			}
+		}
+
+		dev->ac97_recgain = ac97_control_find(dev->ac97,
+		    AUDIO_CTRL_ID_RECGAIN);
+		dev->ac97_recsrc = ac97_control_find(dev->ac97,
+		    AUDIO_CTRL_ID_RECSRC);
+	}
+
+	audigyls_add_controls(dev);
+
+	if (dev->ac97) {
+		ac97_register_controls(dev->ac97);
+	}
+
+	if (audigyls_alloc_port(dev, AUDIGYLS_PLAY_PORT) != DDI_SUCCESS)
+		goto error;
+	if (audigyls_alloc_port(dev, AUDIGYLS_REC_PORT) != DDI_SUCCESS)
+		goto error;
+
+	audigyls_hwinit(dev);
+
+	audigyls_configure_mixer(dev);
+
+	/* set up kernel statistics */
+	if ((dev->ksp = kstat_create(AUDIGYLS_NAME, ddi_get_instance(dip),
+	    AUDIGYLS_NAME, "controller", KSTAT_TYPE_INTR, 1,
+	    KSTAT_FLAG_PERSISTENT)) != NULL) {
+		kstat_install(dev->ksp);
+	}
+
+	if (audio_dev_register(dev->adev) != DDI_SUCCESS) {
+		audio_dev_warn(dev->adev, "unable to register with framework");
+		goto error;
+	}
+
+	(void) ddi_intr_enable(dev->ih);
+	ddi_report_dev(dip);
+
+	return (DDI_SUCCESS);
+
+error:
+	audigyls_destroy(dev);
+	return (DDI_FAILURE);
+}
+
+int
+audigyls_resume(dev_info_t *dip)
+{
+	audigyls_dev_t *dev;
+	audigyls_port_t *port;
+
+	dev = ddi_get_driver_private(dip);
+
+	for (int i = 0; i < AUDIGYLS_NUM_PORT; i++) {
+		port = dev->port[i];
+		audio_engine_reset(port->engine);
+	}
+
+	audigyls_hwinit(dev);
+
+	/* allow ac97 operations again */
+	if (dev->ac97)
+		ac97_resume(dev->ac97);
+
+	audigyls_configure_mixer(dev);
+
+	mutex_enter(&dev->mutex);
+	dev->suspended = B_FALSE;
+
+	for (int i = 0; i < AUDIGYLS_NUM_PORT; i++) {
+
+		port = dev->port[i];
+
+		audigyls_reset_port(port);
+
+		if (port->started) {
+			audigyls_start_port(port);
+		}
+	}
+
+	mutex_exit(&dev->mutex);
+	return (DDI_SUCCESS);
+}
+
+int
+audigyls_detach(audigyls_dev_t *dev)
+{
+	if (audio_dev_unregister(dev->adev) != DDI_SUCCESS)
+		return (DDI_FAILURE);
+
+	audigyls_destroy(dev);
+	return (DDI_SUCCESS);
+}
+
+int
+audigyls_suspend(audigyls_dev_t *dev)
+{
+	if (dev->ac97)
+		ac97_suspend(dev->ac97);
+
+	mutex_enter(&dev->mutex);
+	for (int i = 0; i < AUDIGYLS_NUM_PORT; i++) {
+
+		audigyls_port_t *port = dev->port[i];
+		audigyls_stop_port(port);
+	}
+	dev->suspended = B_TRUE;
+	mutex_exit(&dev->mutex);
+	return (DDI_SUCCESS);
+}
+
+static int audigyls_ddi_attach(dev_info_t *, ddi_attach_cmd_t);
+static int audigyls_ddi_detach(dev_info_t *, ddi_detach_cmd_t);
+static int audigyls_ddi_quiesce(dev_info_t *);
+
+static struct dev_ops audigyls_dev_ops = {
+	DEVO_REV,		/* rev */
+	0,			/* refcnt */
+	NULL,			/* getinfo */
+	nulldev,		/* identify */
+	nulldev,		/* probe */
+	audigyls_ddi_attach,	/* attach */
+	audigyls_ddi_detach,	/* detach */
+	nodev,			/* reset */
+	NULL,			/* cb_ops */
+	NULL,			/* bus_ops */
+	NULL,			/* power */
+	audigyls_ddi_quiesce,	/* quiesce */
+};
+
+static struct modldrv audigyls_modldrv = {
+	&mod_driverops,			/* drv_modops */
+	"Creative Audigy LS Audio",		/* linkinfo */
+	&audigyls_dev_ops,			/* dev_ops */
+};
+
+static struct modlinkage modlinkage = {
+	MODREV_1,
+	{ &audigyls_modldrv, NULL }
+};
+
+int
+_init(void)
+{
+	int	rv;
+
+	audio_init_ops(&audigyls_dev_ops, AUDIGYLS_NAME);
+	if ((rv = mod_install(&modlinkage)) != 0) {
+		audio_fini_ops(&audigyls_dev_ops);
+	}
+	return (rv);
+}
+
+int
+_fini(void)
+{
+	int	rv;
+
+	if ((rv = mod_remove(&modlinkage)) == 0) {
+		audio_fini_ops(&audigyls_dev_ops);
+	}
+	return (rv);
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+	return (mod_info(&modlinkage, modinfop));
+}
+
+int
+audigyls_ddi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+	switch (cmd) {
+	case DDI_ATTACH:
+		return (audigyls_attach(dip));
+
+	case DDI_RESUME:
+		return (audigyls_resume(dip));
+
+	default:
+		return (DDI_FAILURE);
+	}
+}
+
+int
+audigyls_ddi_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+	audigyls_dev_t *dev;
+
+	dev = ddi_get_driver_private(dip);
+
+	switch (cmd) {
+	case DDI_DETACH:
+		return (audigyls_detach(dev));
+
+	case DDI_SUSPEND:
+		return (audigyls_suspend(dev));
+
+	default:
+		return (DDI_FAILURE);
+	}
+}
+
+int
+audigyls_ddi_quiesce(dev_info_t *dip)
+{
+	audigyls_dev_t	*dev;
+	uint32_t status;
+
+	dev = ddi_get_driver_private(dip);
+
+	for (int i = 0; i < AUDIGYLS_NUM_PORT; i++) {
+
+		audigyls_port_t *port = dev->port[i];
+		audigyls_stop_port(port);
+	}
+
+	/*
+	 * Turn off the hardware
+	 */
+
+	write_reg(dev, SA, 0);
+	OUTL(dev, IER, 0);	/* Interrupt disable */
+	write_reg(dev, AIE, 0);	/* Disable audio interrupts */
+	status = INL(dev, IPR);
+	OUTL(dev, IPR, status);	/* Acknowledge */
+	return (DDI_SUCCESS);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/io/audio/drv/audiols/audiols.h	Mon Aug 31 23:25:09 2009 -0700
@@ -0,0 +1,282 @@
+/*
+ * 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.
+ */
+
+/*
+ * Purpose: Definitions for the Creative Audigy LS driver
+ */
+/*
+ * This file is part of Open Sound System
+ *
+ * Copyright (C) 4Front Technologies 1996-2009.
+ *
+ * This software is released under CDDL 1.0 source license.
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ */
+#ifndef	AUDIGYLS_H
+#define	AUDIGYLS_H
+
+#define	AUDIGYLS_NAME		"audiols"
+
+#define	AUDIGYLS_NUM_PORT	2
+#define	AUDIGYLS_PLAY_PORT	0
+#define	AUDIGYLS_REC_PORT	1
+
+/*
+ * Number of fragments must be multiple of 2 because the
+ * hardware supports only full and half buffer interrupts. In
+ * addition it looks like 8 fragments is the minimum.
+ */
+#define	AUDIGYLS_NUM_FRAGS		(8*2)
+
+#define	PCI_VENDOR_ID_CREATIVE 		0x1102
+#define	PCI_DEVICE_ID_CREATIVE_AUDIGYLS 0x0007
+
+#define	AUDIGYLS_MAX_INTRS		256
+#define	AUDIGYLS_MIN_INTRS		24
+#define	AUDIGYLS_INTRS			100
+
+/*
+ * PCI registers
+ */
+
+#define	PR 	0x00
+#define	DR	0x04
+#define	IPR	0x08
+#define	IER	0x0C
+#define		INTR_PCI	(1 << 0)
+#define		INTR_TXA	(1 << 1)	/* midi-a tx */
+#define		INTR_RXA	(1 << 2)	/* midi-a rx */
+#define		INTR_IT2	(1 << 3)	/* timer 2, 44.1 kHz */
+#define		INTR_IT1	(1 << 4)	/* timer 1, 192 kHz */
+#define		INTR_SS_	(1 << 5)	/* spdif status */
+#define		INTR_SRT	(1 << 6)	/* sample rate status */
+#define		INTR_GP		(1 << 7)
+#define		INTR_AI		(1 << 8)	/* audio pending interrupt */
+#define		INTR_I2CDAC	(1 << 9)
+#define		INTR_I2CEE	(1 << 10)
+#define		INTR_SPI	(1 << 11)
+#define		INTR_SPF	(1 << 12)
+#define		INTR_SUO	(1 << 13)
+#define		INTR_SUI	(1 << 14)
+#define		INTR_TXB	(1 << 16)	/* midi-b tx */
+#define		INTR_RXB	(1 << 17)	/* midi-b rx */
+
+#define	HC	0x14
+#define		HC_PF		(1 << 11)	/* play fmt 1 = 32b, 0 = 16b */
+#define		HC_RF		(1 << 10)	/* rec fmt 1 = 32b, 0 = 16b */
+#define		HC_AC97		(1 << 3)
+#define		HC_AEN		(1 << 0)	/* audio enable */
+
+#define	GPIO	0x18
+#define	AC97D	0x1C
+#define	AC97A	0x1E
+/*
+ * Indirect registers
+ */
+
+#define	PTBA		0x000	/* gather play table base address */
+#define	PTBS		0x001	/* gather play table buffer size */
+#define	PTCA		0x002	/* gather play table current addr ptr */
+#define	PFBA		0x004	/* play fifo base address */
+#define	PFBS		0x005	/* play fifo buffer size */
+#define	CPFA		0x006	/* current play fifo address */
+#define	PFEA		0x007	/* play fifo end address */
+#define	CPCAV		0x008	/* current play fifo offset/cache sz valid */
+#define	RFBA		0x010	/* record fifo base address */
+#define	RFBS		0x011	/* record fifo buffer size */
+#define	CRFA		0x012	/* current record fifo address */
+#define	CRCAV		0x013	/* current record fifo offset/cache sz valid */
+#define	CDL		0x020	/* play fifo cache data, 0x20-0x2f */
+#define	SA		0x040	/* start audio */
+#define	SCS3		0x041
+#define	SCS0		0x042
+#define	SCS1		0x043
+#define	SCS2		0x044
+#define	SPC		0x045	/* spdif output control */
+#define	WMARK		0x046	/* test purposes only */
+#define	SPSC		0x049	/* spdif input control */
+#define	RCD		0x050	/* record cache data, 0x50-0x5f */
+#define	P17RECSEL	0x060	/* record fifo map address */
+#define	P17RECVOLL	0x061	/* record fifo volume control (lo) */
+#define	P17RECVOLH	0x062	/* record fifo volume control (hi) */
+
+#define	HMIXMAP_SPDIF	0x063	/* spdif router map address */
+#define	SMIXMAP_SPDIF	0x064	/* spdif router map address */
+#define	MIXCTL_SPDIF	0x065	/* spdif mixer control */
+#define	MIXVOL_SPDIF	0x066	/* spdif mixer input volume control */
+#define	HMIXMAP_I2S	0x067	/* i2s router map address */
+#define	SMIXMAP_I2S	0x068	/* i2s router map address */
+#define	MIXCTL_I2S	0x069	/* i2s mixer control */
+#define	MIXVOL_I2S	0x06a	/* i2s mixer input volume control */
+
+/* MIDI UART */
+#define	MUDATA		0x06c	/* midi uart a data */
+#define	MUCMDA		0x06d	/* midi uart a command/status */
+#define	MUDATB		0x06e	/* midi uart b data */
+#define	MUCMDB		0x06f	/* midi uart b command/status */
+
+#define	SRT		0x070	/* sample rate tracker status */
+#define	SRCTL		0x071	/* sample rate control */
+#define	AUDCTL		0x072	/* audio output control */
+#define	CHIP_ID		0x074	/* chip id */
+#define	AIE		0x075	/* audio interrupt enable */
+#define	AIP		0x076	/* audio interrupt */
+#define	WALL192		0x077	/* wall clock @ 192 kHz */
+#define	WALL441		0x078	/* wall clock @ 44.1 kHz */
+#define	IT		0x079	/* interval timer */
+#define	SPI		0x07a	/* spi interface */
+#define	I2C_A		0x07b	/* i2c address */
+#define	I2C_0		0x07c	/* i2c data */
+#define	I2C_1		0x07d	/* i2c data */
+
+/*
+ * Audio interrupt bits
+ */
+
+#define	AI_PFH		0x00000001	/* playback fifo half loop */
+#define	AI_PFF		0x00000010	/* playback fifo loop */
+#define	AI_TFH		0x00000100	/* playback table half loop */
+#define	AI_TFF		0x00001000	/* playback table loop */
+#define	AI_RFH		0x00010000	/* capture table half loop */
+#define	AI_RFF		0x00100000	/* capture fifo loop */
+#define	AI_EAI		0x01000000	/* enables audio end interrupt */
+
+#define	SA_48K		0
+#define	SA_44K		1
+#define	SA_96K		2
+#define	SA_192K		3
+
+#define	SA_MIX_OUT_EN(ch)	(1 << ((ch) + 28))
+#define	SA_MIX_IN_EN(ch)	(1 << ((ch) + 24))
+#define	SA_PLAY_RATE(ch, rate)	((rate) << (((ch) * 2) + 16))
+#define	SA_PLAY_START(ch)	(1 << (ch))
+#define	SA_RECORD_START(ch)	(1 << ((ch) + 8))
+
+#define	SA_SPA(ch)	(1U << (ch))
+#define	SA_SRA(ch)	(1U << ((ch) + 8))
+
+#define	RECSEL_SPDIFOUT	0
+#define	RECSEL_I2SOUT	1
+#define	RECSEL_SPDIFIN	2
+#define	RECSEL_I2SIN	3
+#define	RECSEL_AC97	4
+#define	RECSEL_SRC	5
+
+typedef struct _audigyls_dev_t audigyls_dev_t;
+typedef struct _audigyls_port_t audigyls_port_t;
+
+typedef enum {
+	CTL_FRONT = 0,
+	CTL_SURROUND,
+	CTL_CENTER,
+	CTL_LFE,
+	CTL_RECORDVOL,
+	CTL_MONGAIN,
+	CTL_RECSRC,
+	CTL_SPREAD,
+	CTL_LOOP,
+	CTL_NUM		/* must be last */
+} audigyls_ctrl_num_t;
+
+typedef struct audigyls_ctrl
+{
+	audigyls_dev_t		*dev;
+	audio_ctrl_t		*ctrl;
+	audigyls_ctrl_num_t	num;
+	uint64_t		val;
+} audigyls_ctrl_t;
+
+struct _audigyls_port_t
+{
+	audigyls_dev_t *dev;
+	audio_engine_t *engine;
+
+	int			direction;
+	int			started;
+	boolean_t		active;
+
+	unsigned		fragfr;
+	unsigned		fragsz;
+	unsigned		nchan;
+
+	ddi_dma_handle_t	buf_dmah;	/* dma for buffers */
+	ddi_acc_handle_t	buf_acch;
+	uint32_t		buf_paddr;
+	caddr_t			buf_kaddr;
+	uint32_t		buf_size;
+	uint32_t		buf_frames;	/* Buffer size in frames */
+	uint32_t		offset;
+	int			syncdir;
+	uint64_t		count;
+};
+
+struct _audigyls_dev_t
+{
+	dev_info_t		*dip;
+	audio_dev_t		*adev;
+	ac97_t			*ac97;
+	kstat_t			*ksp;
+	unsigned		intrs;
+	unsigned		timer;
+
+	int			nactive;	/* Num active ports */
+	char			digital_enable;	/* Orange combo-jack mode */
+
+	boolean_t		suspended;
+	ddi_acc_handle_t	pcih;
+	ddi_acc_handle_t	regsh;
+	caddr_t			base;
+	kmutex_t		mutex;		/* For normal locking */
+	kmutex_t		low_mutex;	/* For low level routines */
+	ddi_intr_handle_t	ih;
+
+	audigyls_port_t		*port[AUDIGYLS_NUM_PORT];
+	audigyls_ctrl_t		controls[CTL_NUM];
+
+	ac97_ctrl_t		*ac97_recgain;
+	ac97_ctrl_t		*ac97_recsrc;
+	uint64_t		recmask;
+};
+
+#define	INB(dev, reg)		\
+	ddi_get8(dev->regsh, (void *)(dev->base + reg))
+#define	OUTB(dev, reg, val)	\
+	ddi_put8(dev->regsh, (void *)(dev->base + reg), (val))
+
+#define	INW(dev, reg)		\
+	ddi_get16(dev->regsh, (void *)(dev->base + reg))
+#define	OUTW(dev, reg, val)	\
+	ddi_put16(dev->regsh, (void *)(dev->base + reg), (val))
+
+#define	INL(dev, reg)		\
+	ddi_get32(dev->regsh, (void *)(dev->base + reg))
+#define	OUTL(dev, reg, val)	\
+	ddi_put32(dev->regsh, (void *)(dev->base + reg), (val))
+
+#define	AUDIGYLS_KIOP(X)	((kstat_intr_t *)(X->ksp->ks_data))
+
+#endif /* AUDIGYLS_H */
--- a/usr/src/uts/intel/Makefile.intel.shared	Mon Aug 31 23:03:25 2009 -0700
+++ b/usr/src/uts/intel/Makefile.intel.shared	Mon Aug 31 23:25:09 2009 -0700
@@ -203,6 +203,7 @@
 DRV_KMODS	+= audioens
 DRV_KMODS	+= audiohd
 DRV_KMODS	+= audioixp
+DRV_KMODS	+= audiols
 DRV_KMODS	+= audiopci
 DRV_KMODS	+= audiots
 DRV_KMODS	+= audiovia823x
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/intel/audiols/Makefile	Mon Aug 31 23:25: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/audiols/Makefile
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+#	This makefile drives the production of the audiols driver.
+#
+
+#
+#	Path to the base of the uts directory tree (usually /usr/src/uts).
+#
+UTSBASE = ../..
+
+#
+#	Define the module and object file sets.
+#
+MODULE		= audiols
+OBJECTS		= $(AUDIOLS_OBJS:%=$(OBJS_DIR)/%)
+LINTS		= $(AUDIOLS_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 -Nmisc/ac97
+
+#
+#	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
--- a/usr/src/uts/sparc/Makefile.sparc.shared	Mon Aug 31 23:03:25 2009 -0700
+++ b/usr/src/uts/sparc/Makefile.sparc.shared	Mon Aug 31 23:25:09 2009 -0700
@@ -263,7 +263,7 @@
 #
 #	Machine Specific Driver Modules (/kernel/drv):
 #
-DRV_KMODS	+= audio audio1575 audioens audiocs audiots audiopci
+DRV_KMODS	+= audio audio1575 audioens audiocs audiols audiots audiopci
 DRV_KMODS	+= bge bpp dmfe eri esp fas hme qfe
 DRV_KMODS	+= openeepr options sd ses st
 DRV_KMODS	+= ssd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/sparc/audiols/Makefile	Mon Aug 31 23:25: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/sparc/audiols/Makefile
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+#	This makefile drives the production of the audioens driver.
+#
+
+#
+#	Path to the base of the uts directory tree (usually /usr/src/uts).
+#
+UTSBASE = ../..
+
+#
+#	Define the module and object file sets.
+#
+MODULE		= audiols
+OBJECTS		= $(AUDIOLS_OBJS:%=$(OBJS_DIR)/%)
+LINTS		= $(AUDIOLS_OBJS:%.o=$(LINTS_DIR)/%.ln)
+ROOTMODULE	= $(ROOT_DRV_DIR)/$(MODULE)
+
+#
+#	Include common rules.
+#
+include $(UTSBASE)/sparc/Makefile.sparc
+
+#
+#	Define targets
+#
+ALL_TARGET	= $(BINARY)
+LINT_TARGET	= $(MODULE).lint
+INSTALL_TARGET	= $(BINARY) $(ROOTMODULE)
+
+LDFLAGS		+= -dy -Ndrv/audio -Nmisc/ac97
+
+#
+#	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)/sparc/Makefile.targ