519 RFE audiocmihd
authorGarrett D'Amore <garrett@nexenta.com>
Mon, 31 Jan 2011 17:40:15 -0800
changeset 13278 dabee83e3bb7
parent 13277 6e9c2509dda5
child 13279 bf4390c83614
519 RFE audiocmihd Reviewed by: [email protected] Reviewed by: [email protected] Reviewed by: [email protected] Reviewed by: [email protected] Approved by: [email protected]
usr/src/pkg/manifests/driver-audio-audiocmihd.mf
usr/src/uts/common/Makefile.files
usr/src/uts/common/Makefile.rules
usr/src/uts/common/io/audio/drv/audiocmihd/audiocmihd.c
usr/src/uts/common/io/audio/drv/audiocmihd/audiocmihd.h
usr/src/uts/intel/Makefile.intel.shared
usr/src/uts/intel/audiocmihd/Makefile
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/pkg/manifests/driver-audio-audiocmihd.mf	Mon Jan 31 17:40:15 2011 -0800
@@ -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 (c) 2010, Oracle and/or its affiliates. All rights reserved.
+#
+
+#
+# Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
+#
+
+#
+# The default for payload-bearing actions in this package is to appear in the
+# global zone only.  See the include file for greater detail, as well as
+# information about overriding the defaults.
+#
+<include global_zone_only_component>
+set name=pkg.fmri value=pkg:/driver/audio/audiocmihd@$(PKGVERS)
+set name=pkg.description value="Audio device driver for C-Media 8788"
+set name=pkg.summary value="C-Media 8788 Family Audio Driver"
+set name=info.classification value=org.opensolaris.category.2008:Drivers/Media
+set name=variant.arch value=i386
+dir path=kernel group=sys
+dir path=kernel/drv group=sys
+dir path=kernel/drv/$(ARCH64) group=sys
+driver name=audiocmihd alias=pci13f6,8788
+file path=kernel/drv/$(ARCH64)/audiocmihd group=sys
+file path=kernel/drv/audiocmihd group=sys
+license lic_CDDL license=lic_CDDL
--- a/usr/src/uts/common/Makefile.files	Mon Jan 31 15:13:01 2011 -0800
+++ b/usr/src/uts/common/Makefile.files	Mon Jan 31 17:40:15 2011 -0800
@@ -24,6 +24,10 @@
 #
 
 #
+# Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
+#
+
+#
 # This Makefile defines all file modules for the directory uts/common
 # and its children. These are the source files which may be considered
 # common to all SunOS systems.
@@ -452,6 +456,8 @@
 
 AUDIOCMI_OBJS += audiocmi.o
 
+AUDIOCMIHD_OBJS += audiocmihd.o
+
 AUDIOHD_OBJS +=	audiohd.o
 
 AUDIOIXP_OBJS += audioixp.o
--- a/usr/src/uts/common/Makefile.rules	Mon Jan 31 15:13:01 2011 -0800
+++ b/usr/src/uts/common/Makefile.rules	Mon Jan 31 17:40:15 2011 -0800
@@ -24,6 +24,10 @@
 #
 
 #
+# Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
+#
+
+#
 # uts/common/Makefile.rules
 #
 #	This Makefile defines all the file build rules for the directory
@@ -604,6 +608,10 @@
 	$(COMPILE.c) -o $@ $<
 	$(CTFCONVERT_O)
 
+$(OBJS_DIR)/%.o:		$(UTSBASE)/common/io/audio/drv/audiocmihd/%.c
+	$(COMPILE.c) -o $@ $<
+	$(CTFCONVERT_O)
+
 $(OBJS_DIR)/%.o:		$(UTSBASE)/common/io/audio/drv/audiohd/%.c
 	$(COMPILE.c) -o $@ $<
 	$(CTFCONVERT_O)
@@ -1955,6 +1963,9 @@
 $(LINTS_DIR)/%.ln:		$(UTSBASE)/common/io/audio/drv/audiocmi/%.c
 	@($(LHEAD) $(LINT.c) $< $(LTAIL))
 
+$(LINTS_DIR)/%.ln:		$(UTSBASE)/common/io/audio/drv/audiocmihd/%.c
+	@($(LHEAD) $(LINT.c) $< $(LTAIL))
+
 $(LINTS_DIR)/%.ln:		$(UTSBASE)/common/io/audio/drv/audioens/%.c
 	@($(LHEAD) $(LINT.c) $< $(LTAIL))
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/io/audio/drv/audiocmihd/audiocmihd.c	Mon Jan 31 17:40:15 2011 -0800
@@ -0,0 +1,1804 @@
+/*
+ * 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 CMedia 8788 sound card
+ */
+/*
+ *
+ * Copyright (C) 4Front Technologies 1996-2011.
+ *
+ * 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.
+ */
+
+#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/sysmacros.h>
+#include <sys/note.h>
+#include <sys/audio/audio_driver.h>
+#include <sys/audio/ac97.h>
+
+#include "audiocmihd.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 */
+	0x0,			/* dma_attr_addr_lo */
+	0xffffffffU,		/* dma_attr_addr_hi */
+	0x3ffff,		/* dma_attr_count_max */
+	0x8,			/* dma_attr_align */
+	0x7f,			/* dma_attr_burstsizes */
+	0x1,			/* dma_attr_minxfer */
+	0x3ffff,		/* dma_attr_maxxfer */
+	0x3ffff,		/* dma_attr_seg */
+	0x1,			/* dma_attr_sgllen */
+	0x1,			/* dma_attr_granular */
+	0			/* dma_attr_flags */
+};
+
+
+static int cmediahd_attach(dev_info_t *);
+static int cmediahd_resume(dev_info_t *);
+static int cmediahd_detach(cmediahd_devc_t *);
+static int cmediahd_suspend(cmediahd_devc_t *);
+
+static int cmediahd_open(void *, int, unsigned *, caddr_t *);
+static void cmediahd_close(void *);
+static int cmediahd_start(void *);
+static void cmediahd_stop(void *);
+static int cmediahd_format(void *);
+static int cmediahd_channels(void *);
+static int cmediahd_rate(void *);
+static uint64_t cmediahd_count(void *);
+static void cmediahd_sync(void *, unsigned);
+static void cmediahd_chinfo(void *, int, unsigned *, unsigned *);
+
+
+static uint16_t cmediahd_read_ac97(void *, uint8_t);
+static void cmediahd_write_ac97(void *, uint8_t, uint16_t);
+static int cmediahd_alloc_port(cmediahd_devc_t *, int);
+static void cmediahd_reset_port(cmediahd_portc_t *);
+static void cmediahd_destroy(cmediahd_devc_t *);
+static void cmediahd_hwinit(cmediahd_devc_t *);
+static void cmediahd_refresh_mixer(cmediahd_devc_t *devc);
+static uint32_t mix_scale(uint32_t, int8_t);
+static void cmediahd_ac97_hwinit(cmediahd_devc_t *);
+static void cmediahd_del_controls(cmediahd_devc_t *);
+
+
+static audio_engine_ops_t cmediahd_engine_ops = {
+	AUDIO_ENGINE_VERSION,
+	cmediahd_open,
+	cmediahd_close,
+	cmediahd_start,
+	cmediahd_stop,
+	cmediahd_count,
+	cmediahd_format,
+	cmediahd_channels,
+	cmediahd_rate,
+	cmediahd_sync,
+	NULL, 	/* qlen */
+	cmediahd_chinfo,
+	NULL	/* playahead */
+};
+
+#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 const char mix_cvt[101] = {
+	0, 0, 3, 7, 10, 13, 16, 19,
+	21, 23, 26, 28, 30, 32, 34, 35,
+	37, 39, 40, 42,	43, 45, 46, 47,
+	49, 50, 51, 52, 53, 55, 56, 57,
+	58, 59, 60, 61, 62, 63, 64, 65,
+	65, 66, 67, 68, 69, 70, 70, 71,
+	72, 73, 73, 74, 75, 75, 76, 77,
+	77, 78, 79, 79, 80, 81, 81, 82,
+	82, 83, 84, 84, 85, 85, 86, 86,
+	87, 87, 88, 88, 89, 89, 90, 90,
+	91, 91, 92, 92, 93, 93, 94, 94,
+	95, 95, 96, 96, 96, 97, 97, 98,
+	98, 98, 99, 99, 100
+};
+
+static uint32_t
+mix_scale(uint32_t vol, int8_t bits)
+{
+	vol = mix_cvt[vol];
+	vol = (vol * ((1 << bits) - 1)) / 100;
+	return (vol);
+}
+
+static uint16_t
+cmediahd_read_ac97(void *arg, uint8_t reg)
+{
+	cmediahd_devc_t *devc = arg;
+	uint32_t val;
+	uint16_t data;
+
+	mutex_enter(&devc->low_mutex);
+	val = 0L;
+	val |= reg << 16;
+	val |= 0 << 24;			/* codec 0 or codec 1 */
+	val |= 1 << 23;			/* ac97 read the reg address */
+	OUTL(devc, val, AC97_CMD_DATA);
+	drv_usecwait(100);
+	data = INL(devc, AC97_CMD_DATA) & 0xFFFF;
+	mutex_exit(&devc->low_mutex);
+	return (data);
+}
+
+static void
+cmediahd_write_ac97(void *arg, uint8_t reg, uint16_t data)
+{
+	cmediahd_devc_t *devc = arg;
+	uint32_t val;
+
+	mutex_enter(&devc->low_mutex);
+	val = 0L;
+	val |= reg << 16;
+	val |= data & 0xFFFF;
+	val |= 0 << 24;			/* on board codec or frontpanel */
+	val |= 0 << 23;			/* ac97 write operation */
+	OUTL(devc, val, AC97_CMD_DATA);
+	drv_usecwait(100);
+	mutex_exit(&devc->low_mutex);
+}
+
+#if 0	/* Front Panel AC'97 not supported yet */
+static uint16_t
+cmediahd_read_fp_ac97(void *arg, uint8_t reg)
+{
+	cmediahd_devc_t *devc = arg;
+	uint32_t val;
+	uint16_t data;
+
+	mutex_enter(&devc->low_mutex);
+	val = 0L;
+	val |= 1 << 24;			/* front panel */
+	val |= 1 << 23;			/* ac97 read the reg address */
+	val |= reg << 16;
+	OUTL(devc, val, AC97_CMD_DATA);
+	drv_usecwait(100);
+	data = INL(devc, AC97_CMD_DATA) & 0xFFFF;
+	mutex_exit(&devc->low_mutex);
+
+	return (data);
+}
+
+static void
+cmediahd_write_fp_ac97(void *arg, uint8_t reg, uint16_t data)
+{
+	cmediahd_devc_t *devc = arg;
+	uint32_t val;
+
+	mutex_enter(&devc->low_mutex);
+	val = 0L;
+	val |= 1 << 24;			/* frontpanel */
+	val |= 0 << 23;			/* ac97 write operation */
+	val |= reg << 16;
+	val |= data & 0xFFFF;
+	OUTL(devc, val, AC97_CMD_DATA);
+	drv_usecwait(100);
+	mutex_exit(&devc->low_mutex);
+}
+#endif
+
+static void
+spi_write(void *arg, int codec_num, unsigned char reg, int val)
+{
+	cmediahd_devc_t *devc = arg;
+	unsigned int tmp;
+	int latch, shift, count;
+
+	mutex_enter(&devc->low_mutex);
+
+	/* check if SPI is busy */
+	count = 10;
+	while ((INB(devc, SPI_CONTROL) & 0x1) && count-- > 0) {
+		drv_usecwait(10);
+	}
+
+	if (devc->model == SUBID_XONAR_DS) {
+		shift = 9;
+		latch = 0;
+	} else {
+		shift = 8;
+		latch = 0x80;
+	}
+
+	/* 2 byte data/reg info to be written */
+	tmp = val;
+	tmp |= (reg << shift);
+
+	/* write 2-byte data values */
+	OUTB(devc, tmp & 0xff, SPI_DATA + 0);
+	OUTB(devc, (tmp >> 8) & 0xff, SPI_DATA + 1);
+
+	/* Latch high, clock=160, Len=2byte, mode=write */
+	tmp = (INB(devc, SPI_CONTROL) & ~0x7E) | latch | 0x1;
+
+	/* now address which codec you want to send the data to */
+	tmp |= (codec_num << 4);
+
+	/* send the command to write the data */
+	OUTB(devc, tmp, SPI_CONTROL);
+
+	mutex_exit(&devc->low_mutex);
+}
+
+static void
+i2c_write(void *arg, unsigned char codec_num, unsigned char reg,
+    unsigned char data)
+{
+	cmediahd_devc_t *devc = arg;
+	int count = 50;
+
+	/* Wait for it to stop being busy */
+	mutex_enter(&devc->low_mutex);
+	while ((INW(devc, TWO_WIRE_CTRL) & 0x1) && (count > 0)) {
+		drv_usecwait(10);
+		count--;
+	}
+
+	if (count == 0) {
+		audio_dev_warn(devc->adev, "Time out on Two-Wire interface");
+		mutex_exit(&devc->low_mutex);
+		return;
+	}
+
+	/* first write the Register Address into the MAP register */
+	OUTB(devc, reg, TWO_WIRE_MAP);
+
+	/* now write the data */
+	OUTB(devc, data, TWO_WIRE_DATA);
+
+	/* select the codec number to address */
+	OUTB(devc, codec_num, TWO_WIRE_ADDR);
+
+	mutex_exit(&devc->low_mutex);
+}
+
+static void
+cs4398_init(void *arg, int codec)
+{
+	cmediahd_devc_t *devc = arg;
+
+	/* Fast Two-Wire. Reduces the wire ready time. */
+	OUTW(devc, 0x0100, TWO_WIRE_CTRL);
+
+	/* Power down, enable control mode. */
+	i2c_write(devc, codec, CS4398_MISC_CTRL,
+	    CS4398_CPEN | CS4398_POWER_DOWN);
+	/*
+	 * Left justified PCM (DAC and 8788 support I2S, but doesn't work.
+	 * Setting it introduces clipping like hell).
+	 */
+	i2c_write(devc, codec, CS4398_MODE_CTRL, 0x00);
+	i2c_write(devc, codec, 3, 0x09);
+	i2c_write(devc, codec, 4, 0x82);	/* PCM Automute */
+	i2c_write(devc, codec, 5, 0x80); 	/* Vol A+B to -64dB */
+	i2c_write(devc, codec, 6, 0x80);
+	i2c_write(devc, codec, 7, 0xf0);	/* soft ramping on */
+
+	/* remove the powerdown flag */
+	i2c_write(devc, codec, CS4398_MISC_CTRL, CS4398_CPEN);
+}
+
+
+static void
+cs4362a_init(void *arg, int codec)
+{
+
+	cmediahd_devc_t *devc = arg;
+
+	OUTW(devc, 0x0100, TWO_WIRE_CTRL);
+
+	/* Power down and enable control port. */
+	i2c_write(devc, codec, CS4362A_MODE1_CTRL,
+	    CS4362A_CPEN | CS4362A_POWER_DOWN);
+	/* Left-justified PCM */
+	i2c_write(devc, codec, CS4362A_MODE2_CTRL, CS4362A_DIF_LJUST);
+	/* Ramp & Automute, re-set DAC defaults. */
+	i2c_write(devc, codec, CS4362A_MODE3_CTRL, 0x84);
+	/* Filter control, DAC defs. */
+	i2c_write(devc, codec, CS4362A_FILTER_CTRL, 0);
+	/* Invert control, DAC defs. */
+	i2c_write(devc, codec, CS4362A_INVERT_CTRL, 0);
+	/* Mixing control, DAC defs. */
+	i2c_write(devc, codec, CS4362A_MIX1_CTRL, 0x24);
+	i2c_write(devc, codec, CS4362A_MIX2_CTRL, 0x24);
+	i2c_write(devc, codec, CS4362A_MIX3_CTRL, 0x24);
+	/* Volume to -64dB. */
+	i2c_write(devc, codec, CS4362A_VOLA_1, 0x40);
+	i2c_write(devc, codec, CS4362A_VOLB_1, 0x40);
+	i2c_write(devc, codec, CS4362A_VOLA_2, 0x40);
+	i2c_write(devc, codec, CS4362A_VOLB_2, 0x40);
+	i2c_write(devc, codec, CS4362A_VOLA_3, 0x40);
+	i2c_write(devc, codec, CS4362A_VOLB_3, 0x40);
+	/* Power up. */
+	i2c_write(devc, codec, CS4362A_MODE1_CTRL, CS4362A_CPEN);
+}
+
+
+static void
+cmediahd_generic_set_play_volume(cmediahd_devc_t *devc, int codec_id,
+    int left, int right)
+
+{
+	spi_write(devc, codec_id, AK4396_LchATTCtl | 0x20, mix_scale(left, 8));
+	spi_write(devc, codec_id, AK4396_RchATTCtl | 0x20, mix_scale(right, 8));
+}
+
+static void
+xonar_d1_set_play_volume(cmediahd_devc_t *devc, int codec_id,
+    int left, int right)
+{
+	switch (codec_id) {
+	case 0:
+		i2c_write(devc, XONAR_DX_FRONTDAC, CS4398_VOLA,
+		    CS4398_VOL(left));
+		i2c_write(devc, XONAR_DX_FRONTDAC, CS4398_VOLB,
+		    CS4398_VOL(right));
+		break;
+	case 1:
+		i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLA_1,
+		    CS4362A_VOL(left));
+		i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLB_1,
+		    CS4362A_VOL(right));
+		break;
+	case 2:
+		i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLA_2,
+		    CS4362A_VOL(left));
+		i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLB_2,
+		    CS4362A_VOL(right));
+		break;
+	case 3:
+		i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLA_3,
+		    CS4362A_VOL(left));
+		i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLB_3,
+		    CS4362A_VOL(right));
+		break;
+	}
+}
+
+static void
+xonar_d2_set_play_volume(cmediahd_devc_t *devc, int codec_id,
+    int left, int right)
+{
+	spi_write(devc, xd2_codec_map[codec_id], 16, mix_scale(left, 8));
+	spi_write(devc, xd2_codec_map[codec_id], 17, mix_scale(right, 8));
+}
+
+static void
+xonar_stx_set_play_volume(cmediahd_devc_t *devc, int codec_id,
+    int left, int right)
+{
+	if (codec_id == 0) {
+		i2c_write(devc, XONAR_STX_FRONTDAC, 16, mix_scale(left, 8));
+		i2c_write(devc, XONAR_STX_FRONTDAC, 17, mix_scale(right, 8));
+	}
+}
+
+static void
+xonar_ds_set_play_volume(cmediahd_devc_t *devc, int codec_id,
+    int left, int right)
+{
+	switch (codec_id) {
+	case 0:		/* front */
+		spi_write(devc, XONAR_DS_FRONTDAC, 0,
+		    mix_scale(left, 7) | 0x180);
+		spi_write(devc, XONAR_DS_FRONTDAC, 1,
+		    mix_scale(right, 7) | 0x180);
+		spi_write(devc, XONAR_DS_FRONTDAC, 3,
+		    mix_scale(left, 7) |0x180);
+		spi_write(devc, XONAR_DS_FRONTDAC, 4,
+		    mix_scale(right, 7) | 0x180);
+		break;
+
+	case 1:		/* side */
+		spi_write(devc, XONAR_DS_SURRDAC, 0,
+		    mix_scale(left, 7) | 0x180);
+		spi_write(devc, XONAR_DS_SURRDAC, 1,
+		    mix_scale(right, 7) | 0x180);
+		break;
+	case 2:		/* rear */
+		spi_write(devc, XONAR_DS_SURRDAC, 4,
+		    mix_scale(left, 7) | 0x180);
+		spi_write(devc, XONAR_DS_SURRDAC, 5,
+		    mix_scale(right, 7) | 0x180);
+		break;
+	case 3:		/* center */
+		spi_write(devc, XONAR_DS_SURRDAC, 6,
+		    mix_scale(left, 7) | 0x180);
+		spi_write(devc, XONAR_DS_SURRDAC, 7,
+		    mix_scale(right, 7) | 0x180);
+		break;
+	}
+}
+
+static void
+cmediahd_set_rec_volume(cmediahd_devc_t *devc, int value)
+{
+	unsigned char left, right;
+
+	left = (value >> 8) & 0xff;
+	right = value & 0xff;
+
+	if (left > 100)
+		left = 100;
+	if (right > 100)
+		right = 100;
+
+	spi_write(devc, XONAR_DS_FRONTDAC, 0xe, mix_scale(left, 8));
+	spi_write(devc, XONAR_DS_FRONTDAC, 0xf, mix_scale(right, 8));
+}
+
+static void
+cmediahd_set_play_volume(cmediahd_devc_t *devc, int codec_id, int value)
+{
+	int left, right;
+
+	left = (value >> 8) & 0xFF;
+	right = (value & 0xFF);
+
+	if (left > 100)
+		left = 100;
+	if (right > 100)
+		right = 100;
+
+	switch (devc->model) {
+	case SUBID_XONAR_D1:
+	case SUBID_XONAR_DX:
+		xonar_d1_set_play_volume(devc, codec_id, left, right);
+		break;
+	case SUBID_XONAR_D2:
+	case SUBID_XONAR_D2X:
+		xonar_d2_set_play_volume(devc, codec_id, left, right);
+		break;
+	case SUBID_XONAR_STX:
+		xonar_stx_set_play_volume(devc, codec_id, left, right);
+		break;
+	case SUBID_XONAR_DS:
+		xonar_ds_set_play_volume(devc, codec_id, left, right);
+		break;
+	default:
+		cmediahd_generic_set_play_volume(devc, codec_id, left, right);
+		break;
+	}
+}
+
+/*
+ * Audio routines
+ */
+
+int
+cmediahd_open(void *arg, int flag, unsigned *nframesp, caddr_t *bufp)
+{
+	cmediahd_portc_t *portc = arg;
+
+        _NOTE(ARGUNUSED(flag));
+
+	portc->count = 0;
+
+	*nframesp = portc->nframes;
+	*bufp = portc->kaddr;
+
+	return (0);
+}
+
+void
+cmediahd_close(void *arg)
+{
+        _NOTE(ARGUNUSED(arg));
+}
+
+int
+cmediahd_start(void *arg)
+{
+	cmediahd_portc_t	*portc = arg;
+	cmediahd_devc_t		*devc = portc->devc;
+
+	mutex_enter(&devc->mutex);
+	portc->offset = 0;
+
+	cmediahd_reset_port(portc);
+
+	switch (portc->direction) {
+	case CMEDIAHD_PLAY:
+		/* enable the dma */
+		OUTW(devc, INW(devc, DMA_START) | 0x10, DMA_START);
+		break;
+
+	case CMEDIAHD_REC:
+		/* enable the channel */
+		OUTW(devc, INW(devc, DMA_START) | (1<<devc->rec_eng.chan),
+		    DMA_START);
+		break;
+	}
+
+	mutex_exit(&devc->mutex);
+	return (0);
+}
+
+void
+cmediahd_stop(void *arg)
+{
+	cmediahd_portc_t	*portc = arg;
+	cmediahd_devc_t		*devc = portc->devc;
+
+	mutex_enter(&devc->mutex);
+	switch (portc->direction) {
+	case CMEDIAHD_PLAY:
+		/* disable dma */
+		OUTW(devc, INW(devc, DMA_START) & ~0x10, DMA_START);
+		break;
+
+	case CMEDIAHD_REC:
+		/* disable dma */
+		OUTW(devc, INW(devc, DMA_START) & ~(1<<devc->rec_eng.chan),
+		    DMA_START);
+		break;
+	}
+	mutex_exit(&devc->mutex);
+}
+
+int
+cmediahd_format(void *arg)
+{
+	_NOTE(ARGUNUSED(arg));
+
+	return (AUDIO_FORMAT_S16_LE);
+}
+
+int
+cmediahd_channels(void *arg)
+{
+	cmediahd_portc_t	*portc = arg;
+
+	return (portc->chans);
+}
+
+int
+cmediahd_rate(void *arg)
+{
+	_NOTE(ARGUNUSED(arg));
+
+	return (48000);
+}
+
+void
+cmediahd_sync(void *arg, unsigned nframes)
+{
+	cmediahd_portc_t *portc = arg;
+	_NOTE(ARGUNUSED(nframes));
+
+	(void) ddi_dma_sync(portc->buf_dmah, 0, 0, portc->syncdir);
+}
+
+static void
+cmediahd_chinfo(void *arg, int chan, unsigned *offset, unsigned *incr)
+{
+	cmediahd_portc_t *portc = arg;
+	static const int map8ch[] = { 0, 1, 4, 5, 2, 3, 6, 7 };
+	static const int map4ch[] = { 0, 1, 2, 3 };
+
+	if (portc->chans <= 4) {
+		*offset = map4ch[chan];
+	} else {
+		*offset = map8ch[chan];
+	}
+	*incr = portc->chans;
+}
+
+uint64_t
+cmediahd_count(void *arg)
+{
+	cmediahd_portc_t	*portc = arg;
+	cmediahd_devc_t	*devc = portc->devc;
+	uint64_t	count;
+	uint32_t	offset;
+
+	mutex_enter(&devc->mutex);
+
+	if (portc->direction == CMEDIAHD_PLAY)
+		offset = portc->bufsz/4 - INL(devc, MULTICH_SIZE) + 1;
+	else
+		offset = portc->bufsz/4 - INW(devc, devc->rec_eng.size) + 1;
+
+	/* check for wrap */
+	if (offset < portc->offset) {
+		count = ((portc->bufsz/4) - portc->offset) + offset;
+	} else {
+		count = offset - portc->offset;
+	}
+	portc->count += count;
+	portc->offset = offset;
+
+	/* convert from 16-bit stereo */
+	count = portc->count / (portc->chans/2);
+	mutex_exit(&devc->mutex);
+
+	return (count);
+}
+
+/* private implementation bits */
+
+
+void
+cmediahd_reset_port(cmediahd_portc_t *portc)
+{
+	cmediahd_devc_t *devc = portc->devc;
+	int channels;
+
+	if (devc->suspended)
+		return;
+
+	portc->offset = 0;
+
+	switch (portc->direction) {
+
+	case CMEDIAHD_PLAY:
+		/* reset channel */
+		OUTB(devc, INB(devc, CHAN_RESET)|0x10, CHAN_RESET);
+		drv_usecwait(10);
+		OUTB(devc, INB(devc, CHAN_RESET) & ~0x10, CHAN_RESET);
+		drv_usecwait(10);
+
+		OUTL(devc, portc->paddr,  MULTICH_ADDR);
+		OUTL(devc, (portc->bufsz/4) - 1, MULTICH_SIZE);
+		OUTL(devc, (portc->bufsz/4) - 1, MULTICH_FRAG);
+
+		switch (portc->chans) {
+		case 2:
+			channels = 0;
+			break;
+		case 4:
+			channels = 1;
+			break;
+		case 6:
+			channels = 2;
+			break;
+		case 8:
+			channels = 3;
+			break;
+		}
+		OUTB(devc, (INB(devc, MULTICH_MODE) & ~0x3) | channels,
+		    MULTICH_MODE);
+
+		/* set the format bits in play format register */
+		OUTB(devc, (INB(devc, PLAY_FORMAT) & ~0xC) | 0x0, PLAY_FORMAT);
+		break;
+
+	case CMEDIAHD_REC:
+		OUTB(devc, INB(devc, CHAN_RESET) | (1 << devc->rec_eng.chan),
+		    CHAN_RESET);
+		drv_usecwait(10);
+		OUTB(devc, INB(devc, CHAN_RESET) & ~(1 << devc->rec_eng.chan),
+		    CHAN_RESET);
+		drv_usecwait(10);
+
+		OUTL(devc, portc->paddr,  devc->rec_eng.addr);
+		OUTW(devc, (portc->bufsz/4) - 1, devc->rec_eng.size);
+		OUTW(devc, (portc->bufsz/4) - 1, devc->rec_eng.frag);
+
+
+		switch (portc->chans) {
+		case 2:
+			channels = 0x0;
+			break;
+		case 4:
+			channels = 0x1;
+			break;
+		case 6:
+			channels = 0x2;
+			break;
+		case 8:
+			channels = 0x4;
+			break;
+		default:
+			/* Stereo - boomer only supports stereo */
+			channels = 0x0;
+			break;
+		}
+
+		OUTB(devc, (INB(devc, REC_MODE) & ~0x3) | channels, REC_MODE);
+		OUTB(devc, (INB(devc, REC_FORMAT) & ~0x3) | 0x0, REC_FORMAT);
+
+	}
+}
+
+int
+cmediahd_alloc_port(cmediahd_devc_t *devc, int num)
+{
+	cmediahd_portc_t	*portc;
+	size_t			len;
+	ddi_dma_cookie_t	cookie;
+	uint_t			count;
+	int			dir;
+	unsigned		caps;
+	audio_dev_t		*adev;
+
+	adev = devc->adev;
+	portc = kmem_zalloc(sizeof (*portc), KM_SLEEP);
+	devc->portc[num] = portc;
+	portc->devc = devc;
+	portc->direction = num;
+
+	switch (num) {
+	case CMEDIAHD_REC:
+		portc->syncdir = DDI_DMA_SYNC_FORKERNEL;
+		portc->chans = 2;
+		caps = ENGINE_INPUT_CAP;
+		dir = DDI_DMA_READ;
+		break;
+	case CMEDIAHD_PLAY:
+		portc->syncdir = DDI_DMA_SYNC_FORDEV;
+		portc->chans = 8;
+		caps = ENGINE_OUTPUT_CAP;
+		dir = DDI_DMA_WRITE;
+		break;
+	default:
+		return (DDI_FAILURE);
+	}
+
+	/*
+	 * Calculate buffer size and frames
+	 */
+	portc->nframes = 2048;
+	portc->bufsz = portc->nframes * portc->chans * 2;
+
+	/* Alloc buffers */
+	if (ddi_dma_alloc_handle(devc->dip, &dma_attr_buf, DDI_DMA_SLEEP, NULL,
+	    &portc->buf_dmah) != DDI_SUCCESS) {
+		audio_dev_warn(adev, "failed to allocate BUF handle");
+		return (DDI_FAILURE);
+	}
+
+	if (ddi_dma_mem_alloc(portc->buf_dmah, CMEDIAHD_BUF_LEN,
+	    &buf_attr, DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL,
+	    &portc->kaddr, &len, &portc->buf_acch) != DDI_SUCCESS) {
+		audio_dev_warn(adev, "failed to allocate BUF memory");
+		return (DDI_FAILURE);
+	}
+
+	bzero(portc->kaddr, len);
+
+	if (ddi_dma_addr_bind_handle(portc->buf_dmah, NULL, portc->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);
+	}
+	portc->paddr = cookie.dmac_address;
+
+	portc->engine = audio_engine_alloc(&cmediahd_engine_ops, caps);
+	if (portc->engine == NULL) {
+		audio_dev_warn(adev, "audio_engine_alloc failed");
+		return (DDI_FAILURE);
+	}
+
+	audio_engine_set_private(portc->engine, portc);
+	audio_dev_add_engine(adev, portc->engine);
+
+	return (DDI_SUCCESS);
+}
+
+void
+cmediahd_destroy(cmediahd_devc_t *devc)
+{
+	mutex_destroy(&devc->mutex);
+	mutex_destroy(&devc->low_mutex);
+
+	for (int i = 0; i < CMEDIAHD_NUM_PORTC; i++) {
+		cmediahd_portc_t *portc = devc->portc[i];
+		if (!portc)
+			continue;
+		if (portc->engine) {
+			audio_dev_remove_engine(devc->adev, portc->engine);
+			audio_engine_free(portc->engine);
+		}
+		if (portc->paddr) {
+			(void) ddi_dma_unbind_handle(portc->buf_dmah);
+		}
+		if (portc->buf_acch) {
+			ddi_dma_mem_free(&portc->buf_acch);
+		}
+		if (portc->buf_dmah) {
+			ddi_dma_free_handle(&portc->buf_dmah);
+		}
+		kmem_free(portc, sizeof (*portc));
+	}
+
+	if (devc->ac97) {
+		ac97_free(devc->ac97);
+	}
+
+	cmediahd_del_controls(devc);
+
+	if (devc->adev != NULL) {
+		audio_dev_free(devc->adev);
+	}
+	if (devc->regsh != NULL) {
+		ddi_regs_map_free(&devc->regsh);
+	}
+	if (devc->pcih != NULL) {
+		pci_config_teardown(&devc->pcih);
+	}
+	kmem_free(devc, sizeof (*devc));
+}
+
+void
+cmediahd_ac97_hwinit(cmediahd_devc_t *devc)
+{
+	/* GPIO #0 programmed as output, set CMI9780 Reg0x70 */
+	cmediahd_write_ac97(devc, 0x70, 0x100);
+
+	/* LI2LI,MIC2MIC; let them always on, FOE on, ROE/BKOE/CBOE off */
+	cmediahd_write_ac97(devc, 0x62, 0x180F);
+
+	/* unmute Master Volume */
+	cmediahd_write_ac97(devc, 0x02, 0x0);
+
+	/* change PCBeep path, set Mix2FR on, option for quality issue */
+	cmediahd_write_ac97(devc, 0x64, 0x8043);
+
+	/* mute PCBeep, option for quality issues */
+	cmediahd_write_ac97(devc, 0x0A, 0x8000);
+
+	/* Record Select Control Register (Index 1Ah) */
+	cmediahd_write_ac97(devc, 0x1A, 0x0000);
+
+	/* set Mic Volume Register 0x0Eh umute and enable micboost */
+	cmediahd_write_ac97(devc, 0x0E, 0x0848);
+
+	/* set Line in Volume Register 0x10h mute */
+	cmediahd_write_ac97(devc, 0x10, 0x8808);
+
+	/* set CD Volume Register 0x12h mute */
+	cmediahd_write_ac97(devc, 0x12, 0x8808);
+
+	/* set AUX Volume Register 0x16h max */
+	cmediahd_write_ac97(devc, 0x16, 0x0808);
+
+	/* set record gain Register 0x1Ch to max */
+	cmediahd_write_ac97(devc, 0x1C, 0x0F0F);
+
+	/* GPIO status  register enable GPO0 */
+	cmediahd_write_ac97(devc, 0x72, 0x0001);
+}
+void
+cmediahd_hwinit(cmediahd_devc_t *devc)
+{
+
+	unsigned short sVal;
+	unsigned short i2s_fmt;
+	unsigned char bVal;
+	int i, count;
+
+	/* setup the default rec DMA engines to REC_A */
+	devc->rec_eng.addr = RECA_ADDR;
+	devc->rec_eng.size = RECA_SIZE;
+	devc->rec_eng.frag = RECA_FRAG;
+	devc->rec_eng.i2s = I2S_ADC1;
+	devc->rec_eng.chan = REC_A;
+
+	/* setup GPIOs to 0 */
+	devc->gpio_mic = 0;
+	devc->gpio_out = 0;
+	devc->gpio_codec = 0;
+	devc->gpio_alt = 0;
+
+	/* Init CMI Controller */
+	sVal = INW(devc, CTRL_VERSION);
+	if (!(sVal & 0x0008)) {
+		bVal = INB(devc, MISC_REG);
+		bVal |= 0x20;
+		OUTB(devc, bVal, MISC_REG);
+	}
+
+	bVal = INB(devc, FUNCTION);
+	bVal |= 0x02; /* Reset codec */
+	OUTB(devc, bVal, FUNCTION);
+
+	/* Cold reset onboard AC97 */
+	OUTW(devc, 0x1, AC97_CTRL);
+	count = 100;
+	while ((INW(devc, AC97_CTRL) & 0x2) && (count--)) {
+		OUTW(devc, (INW(devc, AC97_CTRL) & ~0x2) | 0x2, AC97_CTRL);
+		drv_usecwait(100);
+	}
+
+	if (!count)
+		audio_dev_warn(devc->adev, "CMI8788 AC97 not ready");
+
+	sVal = INW(devc, AC97_CTRL);
+	/* check if there's an onboard AC97 codec (CODEC 0) */
+	if (sVal & 0x10) {
+		/* disable CODEC0 OUTPUT */
+		OUTW(devc, INW(devc, AC97_OUT_CHAN_CONFIG) & ~0xFF00,
+		    AC97_OUT_CHAN_CONFIG);
+
+		/* enable CODEC0 INPUT */
+		OUTW(devc, INW(devc, AC97_IN_CHAN_CONFIG) | 0x0300,
+		    AC97_IN_CHAN_CONFIG);
+
+		devc->has_ac97 = 1;
+	}
+
+	/* check if there's an front panel AC97 codec (CODEC1) */
+	if (sVal & 0x20) {
+		/* enable CODEC1 OUTPUT */
+		OUTW(devc, INW(devc, AC97_OUT_CHAN_CONFIG) | 0x0033,
+		    AC97_OUT_CHAN_CONFIG);
+		/* enable CODEC1 INPUT */
+		OUTW(devc, INW(devc, AC97_IN_CHAN_CONFIG) | 0x0033,
+		    AC97_IN_CHAN_CONFIG);
+
+		devc->has_fp_ac97 = 1;
+	}
+
+	/* Disable AC97 interrupts and initialize AC97 */
+	OUTB(devc, 0x0, AC97_INTR_MASK);
+	OUTW(devc, INW(devc, IRQ_MASK) & ~0x4000, IRQ_MASK);
+
+	/* I2S to 16bit/48Khz/Master, see below. */
+	i2s_fmt = 0x011A;
+
+	/* Setup I2S to use 16bit instead of 24Bit */
+	OUTW(devc, i2s_fmt, I2S_MULTICH_DAC);
+	OUTW(devc, i2s_fmt, I2S_ADC1);
+	OUTW(devc, i2s_fmt, I2S_ADC2);
+	OUTW(devc, i2s_fmt, I2S_ADC3);
+
+	/* setup Routing regs (default vals) */
+	OUTW(devc, 0xE400, PLAY_ROUTING);
+	OUTB(devc, 0x00, REC_ROUTING); /* default routing set to I2S */
+	OUTB(devc, 0x00, REC_MONITOR); /* monitor through MULTICH_PLAY */
+	OUTB(devc, 0xE4, MONITOR_ROUTING); /* default monitor routing */
+
+
+	/* Enable Xonar output */
+	switch (devc->model) {
+	case SUBID_XONAR_D1:
+	case SUBID_XONAR_DX:
+		/* GPIO8 = 0x100 controls mic/line-in */
+		/* GPIO0 = 0x001controls output */
+		/* GPIO2/3 = 0x00C codec output control */
+
+		devc->rec_eng.addr = RECB_ADDR;
+		devc->rec_eng.size = RECB_SIZE;
+		devc->rec_eng.frag = RECB_FRAG;
+		devc->rec_eng.i2s = I2S_ADC2;
+		devc->rec_eng.chan = REC_B;
+
+		/* disable AC97 mixer - not used */
+		devc->has_ac97 = 0;
+
+		/* setup for 2wire communication mode */
+		OUTB(devc, INB(devc, FUNCTION) | 0x40, FUNCTION);
+
+		/* setup GPIO direction */
+		OUTW(devc, INW(devc, GPIO_CONTROL) | 0x10D, GPIO_CONTROL);
+		/* setup GPIO pins */
+		OUTW(devc, INW(devc, GPIO_DATA) | 0x101, GPIO_DATA);
+
+		/* init the front and rear dacs */
+		cs4398_init(devc, XONAR_DX_FRONTDAC);
+		cs4362a_init(devc, XONAR_DX_SURRDAC);
+		break;
+
+	case SUBID_XONAR_D2:
+	case SUBID_XONAR_D2X:
+		/* GPIO7 = 0x0080 controls mic/line-in */
+		/* GPIO8 = 0x0100 controls output */
+		/* GPIO2/3 = 0x000C codec output control */
+
+		devc->rec_eng.addr = RECB_ADDR;
+		devc->rec_eng.size = RECB_SIZE;
+		devc->rec_eng.frag = RECB_FRAG;
+		devc->rec_eng.i2s = I2S_ADC2;
+		devc->rec_eng.chan = REC_B;
+
+		/* disable the AC97 mixer - it's not useful */
+		devc->has_ac97 = 0;
+
+		/* setup for spi communication mode */
+		OUTB(devc, (INB(devc, FUNCTION) & ~0x40) | 0x80, FUNCTION);
+		/* setup the GPIO direction */
+		OUTW(devc, INW(devc, GPIO_CONTROL) | 0x18c, GPIO_CONTROL);
+
+		/* setup GPIO Pins */
+		OUTW(devc, INW(devc, GPIO_DATA) | 0x100,  GPIO_DATA);
+
+		/* for all 4 codecs: unmute, set to 24Bit SPI */
+		for (i = 0; i < 4; ++i) {
+			/* left vol */
+			spi_write(devc, i, 16, mix_scale(75, 8));
+			/* right vol */
+			spi_write(devc, i, 17, mix_scale(75, 8));
+			/* unmute/24LSB/ATLD */
+			spi_write(devc, i, 18, 0x30 | 0x80);
+		}
+		break;
+
+	case SUBID_XONAR_STX:
+		devc->rec_eng.addr = RECB_ADDR;
+		devc->rec_eng.size = RECB_SIZE;
+		devc->rec_eng.frag = RECB_FRAG;
+		devc->rec_eng.i2s = I2S_ADC2;
+		devc->rec_eng.chan = REC_B;
+
+		/* disable the AC97 mixer - it's not useful */
+		devc->has_ac97 = 0;
+
+		/* setup for spi communication mode */
+		OUTB(devc, (INB(devc, FUNCTION) & ~0x40) | 0x80, FUNCTION);
+		/* setup the GPIO direction */
+		OUTW(devc, INW(devc, GPIO_CONTROL) | 0x18F, GPIO_CONTROL);
+		/* setup GPIO Pins */
+		OUTW(devc, INW(devc, GPIO_DATA) | 0x111, GPIO_DATA);
+
+		/* init front DAC */
+		/* left vol */
+		i2c_write(devc, XONAR_STX_FRONTDAC, 16, mix_scale(75, 8));
+		/* right vol */
+		i2c_write(devc, XONAR_STX_FRONTDAC, 17, mix_scale(75, 8));
+		/* unmute/24LSB/ATLD */
+		i2c_write(devc, XONAR_STX_FRONTDAC, 18, 0x30 | 0x80);
+		i2c_write(devc, XONAR_STX_FRONTDAC, 19, 0); /* ATS1/FLT_SHARP */
+		i2c_write(devc, XONAR_STX_FRONTDAC, 20, 0); /* OS_64 */
+		i2c_write(devc, XONAR_STX_FRONTDAC, 21, 0);
+		break;
+
+	case SUBID_XONAR_DS:
+		/* GPIO 8 = 1 output enabled 0 mute */
+		/* GPIO 7 = 1 lineout enabled 0 mute */
+		/* GPIO 6 = 1 mic select 0 line-in select */
+		/* GPIO 4 = 1 FP Headphone plugged in */
+		/* GPIO 3 = 1 FP Mic plugged in */
+
+		devc->rec_eng.addr = RECA_ADDR;
+		devc->rec_eng.size = RECA_SIZE;
+		devc->rec_eng.frag = RECA_FRAG;
+		devc->rec_eng.i2s = I2S_ADC1;
+		devc->rec_eng.chan = REC_A;
+
+		/* disable the AC97 mixer - it's not useful */
+		devc->has_ac97 = 0;
+
+		/* setup for spi communication mode */
+		OUTB(devc, (INB(devc, FUNCTION) & ~0x40) | 0x80, FUNCTION);
+		/* setup the GPIO direction */
+		OUTW(devc, INW(devc, GPIO_CONTROL) | 0x1D0, GPIO_CONTROL);
+		/* setup GPIO Pins */
+		OUTW(devc, INW(devc, GPIO_DATA) | 0x1D0, GPIO_DATA);
+		spi_write(devc, XONAR_DS_FRONTDAC, 0x17, 0x1); /* reset */
+		spi_write(devc, XONAR_DS_FRONTDAC, 0x7, 0x90); /* dac control */
+		spi_write(devc, XONAR_DS_FRONTDAC, 0x8, 0); /* unmute */
+		/* powerdown hp */
+		spi_write(devc, XONAR_DS_FRONTDAC, 0xC, 0x22);
+		spi_write(devc, XONAR_DS_FRONTDAC, 0xD, 0x8); /* powerdown hp */
+		spi_write(devc, XONAR_DS_FRONTDAC, 0xA, 0x1); /* LJust/16bit */
+		spi_write(devc, XONAR_DS_FRONTDAC, 0xB, 0x1); /* LJust/16bit */
+		spi_write(devc, XONAR_DS_SURRDAC, 0x1f, 1); /* reset */
+		/* LJust/24bit */
+		spi_write(devc, XONAR_DS_SURRDAC, 0x3, 0x1|0x20);
+		break;
+
+
+	default:
+		/* SPI default for anything else, including the */
+		OUTB(devc, (INB(devc, FUNCTION) & ~0x40) | 0x80, FUNCTION);
+		OUTB(devc, 0x18, REC_ROUTING); /* default routing set to I2S */
+		break;
+	}
+
+	/* only initialize AC97 if not defined */
+	if (devc->has_ac97)
+		cmediahd_ac97_hwinit(devc);
+}
+
+static int
+cmediahd_set_control(void *arg, uint64_t val)
+{
+	cmediahd_ctrl_t	*pc = arg;
+	cmediahd_devc_t	*devc = pc->devc;
+
+	mutex_enter(&devc->mutex);
+
+	pc->val = val;
+
+	switch (pc->num) {
+
+	case CTL_VOLUME:
+	case CTL_FRONT:
+		cmediahd_set_play_volume(devc, 0, val);
+		break;
+
+	case CTL_REAR:
+		cmediahd_set_play_volume(devc, 1, val);
+		break;
+
+	case CTL_CENTER:
+		val &= 0xff;
+		val |= ((devc->controls[CTL_LFE].val) << 8);
+		cmediahd_set_play_volume(devc, 2, val);
+		break;
+
+	case CTL_LFE:
+		val &= 0xff;
+		val <<= 8;
+		val |= (devc->controls[CTL_CENTER].val);
+		cmediahd_set_play_volume(devc, 2, val);
+		break;
+
+	case CTL_SURROUND:
+		cmediahd_set_play_volume(devc, 3, val);
+		break;
+
+	case CTL_MONITOR:
+		/* enable recording  monitor rec 1 and rec2 */
+		if (val)
+			OUTB(devc, INB(devc, REC_MONITOR) | 0xF, REC_MONITOR);
+		else
+			OUTB(devc, INB(devc, REC_MONITOR) & ~0xF, REC_MONITOR);
+		break;
+
+	case CTL_RECSRC:
+		switch (val) {
+		case 1: /* Line */
+			if (devc->model == SUBID_XONAR_DS)
+				OUTW(devc, INW(devc, GPIO_DATA) & ~0x40,
+				    GPIO_DATA);
+
+			if (devc->model == SUBID_XONAR_D1 ||
+			    devc->model == SUBID_XONAR_DX)
+				OUTW(devc, INW(devc, GPIO_DATA) &
+				    ~devc->gpio_mic, GPIO_DATA);
+			cmediahd_write_ac97(devc, 0x72,
+			    cmediahd_read_ac97(devc, 0x72) & ~0x1);
+			cmediahd_write_ac97(devc, 0x1A, 0x0404);
+			break;
+
+		case 2:  /* Mic */
+			if (devc->model == SUBID_XONAR_DS)
+				OUTW(devc, INW(devc, GPIO_DATA) | 0x40,
+				    GPIO_DATA);
+
+			if (devc->model == SUBID_XONAR_D1 ||
+			    devc->model == SUBID_XONAR_DX)
+				OUTW(devc, INW(devc, GPIO_DATA) |
+				    devc->gpio_mic, GPIO_DATA);
+			cmediahd_write_ac97(devc, 0x72,
+			    cmediahd_read_ac97(devc, 0x72) | 0x1);
+			/* Unmute Mic */
+			cmediahd_write_ac97(devc, 0xE,
+			    cmediahd_read_ac97(devc, 0xE) & ~0x8000);
+			/* Mute AUX and Video */
+			cmediahd_write_ac97(devc, 0x12,
+			    cmediahd_read_ac97(devc, 0x12) | 0x8000);
+			cmediahd_write_ac97(devc, 0x16,
+			    cmediahd_read_ac97(devc, 0x16) | 0x8000);
+			cmediahd_write_ac97(devc, 0x1A, 0x0000);
+			break;
+
+		case 4: /* AUX */
+			if (devc->model == SUBID_XONAR_D1 ||
+			    devc->model == SUBID_XONAR_DX)
+				OUTW(devc, INW(devc, GPIO_DATA) |
+				    devc->gpio_mic, GPIO_DATA);
+			cmediahd_write_ac97(devc, 0x72,
+			    cmediahd_read_ac97(devc, 0x72) | 0x1);
+			/* Unmute AUX */
+			cmediahd_write_ac97(devc, 0x16,
+			    cmediahd_read_ac97(devc, 0x16) & ~0x8000);
+			/* Mute CD and Mic */
+			cmediahd_write_ac97(devc, 0x14,
+			    cmediahd_read_ac97(devc, 0x14) | 0x8000);
+			cmediahd_write_ac97(devc, 0x0E,
+			    cmediahd_read_ac97(devc, 0x0E) | 0x8000);
+			cmediahd_write_ac97(devc, 0x1A, 0x0303);
+			break;
+
+		case 8: /* Video (CD) */
+			if (devc->model == SUBID_XONAR_D1 ||
+			    devc->model == SUBID_XONAR_DX)
+				OUTW(devc, INW(devc, GPIO_DATA) |
+				    devc->gpio_mic, GPIO_DATA);
+			cmediahd_write_ac97(devc, 0x72,
+			    cmediahd_read_ac97(devc, 0x72) | 0x1);
+			/* Unmute Video (CD) */
+			cmediahd_write_ac97(devc, 0x14,
+			    cmediahd_read_ac97(devc, 0x14) & ~0x8000);
+			/* Mute AUX and Mic */
+			cmediahd_write_ac97(devc, 0x16,
+			    cmediahd_read_ac97(devc, 0x16) | 0x8000);
+			cmediahd_write_ac97(devc, 0x0E,
+			    cmediahd_read_ac97(devc, 0x0E) | 0x8000);
+			/* set input to video */
+			cmediahd_write_ac97(devc, 0x1A, 0x0202);
+			break;
+		}
+		break;
+
+	case CTL_LOOP:
+		if (val)
+			OUTW(devc, INW(devc, GPIO_DATA) | devc->gpio_alt,
+			    GPIO_DATA);
+		else
+			OUTW(devc, (INW(devc, GPIO_DATA) & ~devc->gpio_alt),
+			    GPIO_DATA);
+		break;
+
+	case CTL_SPREAD:
+		if (val)
+			OUTW(devc, INW(devc, PLAY_ROUTING) & 0x00FF,
+			    PLAY_ROUTING);
+		else
+			OUTW(devc, (INW(devc, PLAY_ROUTING) & 0x00FF) |
+			    0xE400, PLAY_ROUTING);
+		break;
+
+	case CTL_RECGAIN:
+		cmediahd_set_rec_volume(devc, val);
+		break;
+
+	case CTL_MICVOL:
+		if (val)
+			cmediahd_write_ac97(devc, 0x0E,
+			    (0x40 | mix_scale(val, -5)) & ~0x8000);
+		else
+			cmediahd_write_ac97(devc, 0x0E, 0x8000);
+		break;
+
+	case CTL_AUXVOL:
+		if (val)
+			cmediahd_write_ac97(devc, 0x16,
+			    mix_scale(val, -5) & ~0x8000);
+		else
+			cmediahd_write_ac97(devc, 0x16, 0x8000);
+		break;
+
+
+	case CTL_CDVOL:
+		if (val)
+			cmediahd_write_ac97(devc, 0x14,
+			    mix_scale(val, -5) & ~0x8000);
+		else
+			cmediahd_write_ac97(devc, 0x14, 0x8000);
+		break;
+	}
+
+	mutex_exit(&devc->mutex);
+	return (0);
+}
+
+static int
+cmediahd_get_control(void *arg, uint64_t *val)
+{
+	cmediahd_ctrl_t	*pc = arg;
+	cmediahd_devc_t	*devc = pc->devc;
+
+	mutex_enter(&devc->mutex);
+	*val = pc->val;
+	mutex_exit(&devc->mutex);
+	return (0);
+}
+
+static void
+cmediahd_alloc_ctrl(cmediahd_devc_t *devc, uint32_t num, uint64_t val)
+{
+	audio_ctrl_desc_t	desc;
+	cmediahd_ctrl_t		*pc;
+
+	bzero(&desc, sizeof (desc));
+
+	pc = &devc->controls[num];
+	pc->num = num;
+	pc->devc = devc;
+
+
+	switch (num) {
+
+	case CTL_VOLUME:
+		desc.acd_name = AUDIO_CTRL_ID_VOLUME;
+		desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
+		desc.acd_minvalue = 0;
+		desc.acd_maxvalue = 100;
+		desc.acd_flags = PCMVOL;
+		break;
+
+	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 = PCMVOL;
+		break;
+
+	case CTL_REAR:
+		desc.acd_name = AUDIO_CTRL_ID_REAR;
+		desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
+		desc.acd_minvalue = 0;
+		desc.acd_maxvalue = 100;
+		desc.acd_flags = PCMVOL;
+		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 = PCMVOL;
+		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 = PCMVOL;
+		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 = PCMVOL;
+		break;
+
+	case CTL_MONITOR:
+		desc.acd_name = AUDIO_CTRL_ID_MONSRC;
+		desc.acd_type = AUDIO_CTRL_TYPE_BOOLEAN;
+		desc.acd_minvalue = 0;
+		desc.acd_maxvalue = 1;
+		desc.acd_flags = RECCTL;
+		break;
+
+	case CTL_RECSRC:
+		desc.acd_name = AUDIO_CTRL_ID_RECSRC;
+		desc.acd_type = AUDIO_CTRL_TYPE_ENUM;
+		desc.acd_flags = RECCTL;
+		desc.acd_enum[0] = AUDIO_PORT_LINEIN;
+		desc.acd_enum[1] = AUDIO_PORT_MIC;
+
+		if (devc->model == SUBID_XONAR_D2 ||
+		    devc->model == SUBID_XONAR_D2X) {
+			desc.acd_minvalue = 0xF;
+			desc.acd_maxvalue = 0xF;
+			desc.acd_enum[2] = AUDIO_PORT_AUX1IN;
+			desc.acd_enum[3] = AUDIO_PORT_CD;
+		} else {
+			desc.acd_minvalue = 0x3;
+			desc.acd_maxvalue = 0x3;
+		}
+		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;
+
+	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_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 = RECVOL;
+		break;
+
+	case CTL_MICVOL:
+		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;
+		break;
+
+	case CTL_AUXVOL:
+		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;
+		break;
+	case CTL_CDVOL:
+		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;
+		break;
+
+	}
+
+	pc->val = val;
+	pc->ctrl = audio_dev_add_control(devc->adev, &desc,
+	    cmediahd_get_control, cmediahd_set_control, pc);
+}
+
+static void
+cmediahd_refresh_mixer(cmediahd_devc_t *devc)
+{
+	int ctl;
+
+	for (ctl = 0; ctl < CTL_NUM; ctl++) {
+		if (devc->controls[ctl].ctrl == NULL)
+			continue;
+		(void) cmediahd_set_control(&devc->controls[ctl],
+		    devc->controls[ctl].val);
+	}
+}
+
+static void
+cmediahd_add_controls(cmediahd_devc_t *devc)
+{
+	cmediahd_alloc_ctrl(devc, CTL_VOLUME, 80 | (80 << 8));
+	cmediahd_alloc_ctrl(devc, CTL_FRONT, 80 | (80<<8));
+	cmediahd_alloc_ctrl(devc, CTL_REAR, 80 | (80<<8));
+	cmediahd_alloc_ctrl(devc, CTL_CENTER, 80);
+	cmediahd_alloc_ctrl(devc, CTL_LFE, 80);
+	cmediahd_alloc_ctrl(devc, CTL_SURROUND, 80 | (80<<8));
+	cmediahd_alloc_ctrl(devc, CTL_SPREAD, 0);
+	cmediahd_alloc_ctrl(devc, CTL_MONITOR, 0);
+	cmediahd_alloc_ctrl(devc, CTL_LOOP, 0);
+	cmediahd_alloc_ctrl(devc, CTL_RECSRC, 2);
+
+	switch (devc->model) {
+	case SUBID_XONAR_DS:
+		cmediahd_alloc_ctrl(devc, CTL_RECGAIN, 80|80<<8);
+		break;
+	case SUBID_XONAR_D2:
+	case SUBID_XONAR_D2X:
+		cmediahd_alloc_ctrl(devc, CTL_MICVOL, 80|80<<8);
+		cmediahd_alloc_ctrl(devc, CTL_AUXVOL, 80|80<<8);
+		cmediahd_alloc_ctrl(devc, CTL_CDVOL, 80|80<<8);
+		break;
+	}
+
+	cmediahd_refresh_mixer(devc);
+}
+
+void
+cmediahd_del_controls(cmediahd_devc_t *dev)
+{
+	for (int i = 0; i < CTL_NUM; i++) {
+		if (dev->controls[i].ctrl) {
+			audio_dev_del_control(dev->controls[i].ctrl);
+			dev->controls[i].ctrl = NULL;
+		}
+	}
+}
+
+int
+cmediahd_attach(dev_info_t *dip)
+{
+	uint16_t	pci_command, vendor, device, subvendor, subdevice;
+	cmediahd_devc_t	*devc;
+	ddi_acc_handle_t pcih;
+
+	devc = kmem_zalloc(sizeof (*devc), KM_SLEEP);
+	devc->dip = dip;
+	ddi_set_driver_private(dip, devc);
+
+	mutex_init(&devc->mutex, NULL, MUTEX_DRIVER, NULL);
+	mutex_init(&devc->low_mutex, NULL, MUTEX_DRIVER, NULL);
+
+	if ((devc->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(devc->adev, "pci_config_setup failed");
+		goto error;
+	}
+	devc->pcih = pcih;
+
+	vendor = pci_config_get16(pcih, PCI_CONF_VENID);
+	device = pci_config_get16(pcih, PCI_CONF_DEVID);
+	subvendor = pci_config_get16(pcih, PCI_CONF_SUBVENID);
+	subdevice = pci_config_get16(pcih, PCI_CONF_SUBSYSID);
+	if (vendor != PCI_VENDOR_ID_CMEDIA ||
+	    device != PCI_DEVICE_ID_CMEDIAHD) {
+		audio_dev_warn(devc->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, &devc->base, 0, 0, &dev_attr,
+	    &devc->regsh)) != DDI_SUCCESS) {
+		audio_dev_warn(devc->adev, "failed to map registers");
+		goto error;
+	}
+
+	audio_dev_set_description(devc->adev, "CMedia 8788");
+
+	/* Detect Xonar device */
+	if (subvendor == ASUS_VENDOR_ID) {
+		switch (subdevice) {
+		case SUBID_XONAR_D1:
+			audio_dev_set_description(devc->adev,
+			    "Asus Xonar D1 (AV100)");
+			break;
+		case SUBID_XONAR_DX:
+			audio_dev_set_description(devc->adev,
+			    "Asus Xonar DX (AV100)");
+			break;
+		case SUBID_XONAR_D2:
+			audio_dev_set_description(devc->adev,
+			    "Asus Xonar D2 (AV200)");
+			break;
+		case SUBID_XONAR_D2X:
+			audio_dev_set_description(devc->adev,
+			    "Asus Xonar D2X (AV200)");
+			break;
+		case SUBID_XONAR_STX:
+			audio_dev_set_description(devc->adev,
+			    "Asus Xonar STX (AV100)");
+			break;
+		case SUBID_XONAR_DS:
+			audio_dev_set_description(devc->adev,
+			    "Asus Xonar DS (AV66)");
+			break;
+		default:
+			audio_dev_set_description(devc->adev,
+			    "Asus Xonar Unknown Model");
+			subdevice = SUBID_GENERIC;
+			break;
+		}
+		devc->model = subdevice;
+	}
+
+	cmediahd_hwinit(devc);
+
+	if (cmediahd_alloc_port(devc, CMEDIAHD_PLAY) != DDI_SUCCESS)
+		goto error;
+	if (cmediahd_alloc_port(devc, CMEDIAHD_REC) != DDI_SUCCESS)
+		goto error;
+
+	/* Add the AC97 Mixer if there is an onboard AC97 device */
+	if (devc->has_ac97) {
+		devc->ac97 = ac97_alloc(dip, cmediahd_read_ac97,
+		    cmediahd_write_ac97, devc);
+		if (ac97_init(devc->ac97, devc->adev) != DDI_SUCCESS) {
+			audio_dev_warn(devc->adev, "failed to init ac97");
+			goto error;
+		}
+	}
+#if 0
+	/* Add the front panel AC97 device if one exists */
+	if (devc->has_fp_ac97) {
+		devc->fp_ac97 = ac97_alloc(dip, cmediahd_read_fp_ac97,
+		    cmediahd_write_fp_ac97, devc);
+		if (ac97_init(devc->fp_ac97, devc->adev) != DDI_SUCCESS) {
+			audio_dev_warn(devc->adev, "failed to init fp_ac97");
+			goto error;
+		}
+	}
+#endif
+	/* Add the standard CMI8788 Mixer panel */
+	cmediahd_add_controls(devc);
+
+	if (audio_dev_register(devc->adev) != DDI_SUCCESS) {
+		audio_dev_warn(devc->adev, "unable to register with framework");
+		goto error;
+	}
+
+	ddi_report_dev(dip);
+
+	return (DDI_SUCCESS);
+
+error:
+	cmediahd_destroy(devc);
+	return (DDI_FAILURE);
+}
+
+int
+cmediahd_resume(dev_info_t *dip)
+{
+	cmediahd_devc_t *devc;
+
+	devc = ddi_get_driver_private(dip);
+
+	cmediahd_hwinit(devc);
+
+	if (devc->ac97)
+		ac97_reset(devc->ac97);
+
+	cmediahd_refresh_mixer(devc);
+
+	audio_dev_resume(devc->adev);
+
+	return (DDI_SUCCESS);
+}
+
+int
+cmediahd_detach(cmediahd_devc_t *devc)
+{
+	if (audio_dev_unregister(devc->adev) != DDI_SUCCESS)
+		return (DDI_FAILURE);
+
+	cmediahd_destroy(devc);
+	return (DDI_SUCCESS);
+}
+
+int
+cmediahd_suspend(cmediahd_devc_t *devc)
+{
+	audio_dev_suspend(devc->adev);
+	return (DDI_SUCCESS);
+}
+
+static int cmediahd_ddi_attach(dev_info_t *, ddi_attach_cmd_t);
+static int cmediahd_ddi_detach(dev_info_t *, ddi_detach_cmd_t);
+static int cmediahd_ddi_quiesce(dev_info_t *);
+
+static struct dev_ops cmediahd_dev_ops = {
+	DEVO_REV,		/* rev */
+	0,			/* refcnt */
+	NULL,			/* getinfo */
+	nulldev,		/* identify */
+	nulldev,		/* probe */
+	cmediahd_ddi_attach,	/* attach */
+	cmediahd_ddi_detach,	/* detach */
+	nodev,			/* reset */
+	NULL,			/* cb_ops */
+	NULL,			/* bus_ops */
+	NULL,			/* power */
+	cmediahd_ddi_quiesce,	/* quiesce */
+};
+
+static struct modldrv cmediahd_modldrv = {
+	&mod_driverops,			/* drv_modops */
+	"CMedia 8788",			/* linkinfo */
+	&cmediahd_dev_ops,		/* dev_ops */
+};
+
+static struct modlinkage modlinkage = {
+	MODREV_1,
+	{ &cmediahd_modldrv, NULL }
+};
+
+int
+_init(void)
+{
+	int	rv;
+
+	audio_init_ops(&cmediahd_dev_ops, CMEDIAHD_NAME);
+	if ((rv = mod_install(&modlinkage)) != 0) {
+		audio_fini_ops(&cmediahd_dev_ops);
+	}
+	return (rv);
+}
+
+int
+_fini(void)
+{
+	int	rv;
+
+	if ((rv = mod_remove(&modlinkage)) == 0) {
+		audio_fini_ops(&cmediahd_dev_ops);
+	}
+	return (rv);
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+	return (mod_info(&modlinkage, modinfop));
+}
+
+int
+cmediahd_ddi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+	switch (cmd) {
+	case DDI_ATTACH:
+		return (cmediahd_attach(dip));
+
+	case DDI_RESUME:
+		return (cmediahd_resume(dip));
+
+	default:
+		return (DDI_FAILURE);
+	}
+}
+
+int
+cmediahd_ddi_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+	cmediahd_devc_t *devc;
+
+	devc = ddi_get_driver_private(dip);
+
+	switch (cmd) {
+	case DDI_DETACH:
+		return (cmediahd_detach(devc));
+
+	case DDI_SUSPEND:
+		return (cmediahd_suspend(devc));
+
+	default:
+		return (DDI_FAILURE);
+	}
+}
+
+int
+cmediahd_ddi_quiesce(dev_info_t *dip)
+{
+	cmediahd_devc_t	*devc;
+
+	devc = ddi_get_driver_private(dip);
+
+	OUTW(devc, 0x0, DMA_START);
+
+	/*
+	 * Turn off the hardware
+	 */
+
+
+	return (DDI_SUCCESS);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/io/audio/drv/audiocmihd/audiocmihd.h	Mon Jan 31 17:40:15 2011 -0800
@@ -0,0 +1,363 @@
+/*
+ * 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 CMedia 8788 driver.
+ */
+/*
+ * This file is part of Open Sound System
+ *
+ * Copyright (C) 4Front Technologies 1996-2011.
+ *
+ * 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	CMEDIAHD_H
+#define	CMEDIAHD_H
+
+#define	CMEDIAHD_NAME		"audiocmihd"
+
+#define	CMEDIAHD_NUM_PORTC		2
+#define	CMEDIAHD_PLAY			0
+#define	CMEDIAHD_REC			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	CMEDIAHD_BUF_LEN		(65536)
+
+#define	PCI_VENDOR_ID_CMEDIA 		0x13F6
+#define	PCI_DEVICE_ID_CMEDIAHD		0x8788
+
+#define	CMEDIAHD_MAX_INTRS		512
+#define	CMEDIAHD_MIN_INTRS		48
+#define	CMEDIAHD_INTRS			100
+
+/*
+ * PCI registers
+ */
+
+#define	RECA_ADDR		(devc->base+0x00)
+#define	RECA_SIZE		(devc->base+0x04)
+#define	RECA_FRAG		(devc->base+0x06)
+#define	RECB_ADDR		(devc->base+0x08)
+#define	RECB_SIZE		(devc->base+0x0C)
+#define	RECB_FRAG		(devc->base+0x0E)
+#define	RECC_ADDR		(devc->base+0x10)
+#define	RECC_SIZE		(devc->base+0x14)
+#define	RECC_FRAG		(devc->base+0x16)
+#define	SPDIF_ADDR		(devc->base+0x18)
+#define	SPDIF_SIZE		(devc->base+0x1C)
+#define	SPDIF_FRAG		(devc->base+0x1E)
+#define	MULTICH_ADDR		(devc->base+0x20)
+#define	MULTICH_SIZE		(devc->base+0x24)
+#define	MULTICH_FRAG		(devc->base+0x28)
+#define	FPOUT_ADDR		(devc->base+0x30)
+#define	FPOUT_SIZE		(devc->base+0x34)
+#define	FPOUT_FRAG		(devc->base+0x36)
+
+#define	DMA_START		(devc->base+0x40)
+#define	CHAN_RESET		(devc->base+0x42)
+#define	MULTICH_MODE		(devc->base+0x43)
+#define	IRQ_MASK		(devc->base+0x44)
+#define	IRQ_STAT		(devc->base+0x46)
+#define	MISC_REG		(devc->base+0x48)
+#define	REC_FORMAT		(devc->base+0x4A)
+#define	PLAY_FORMAT		(devc->base+0x4B)
+#define	REC_MODE		(devc->base+0x4C)
+#define	FUNCTION		(devc->base+0x50)
+
+#define	I2S_MULTICH_DAC		(devc->base+0x60)
+#define	I2S_ADC1		(devc->base+0x62)
+#define	I2S_ADC2		(devc->base+0x64)
+#define	I2S_ADC3		(devc->base+0x66)
+
+#define	SPDIF_FUNC		(devc->base+0x70)
+#define	SPDIFOUT_CHAN_STAT	(devc->base+0x74)
+#define	SPDIFIN_CHAN_STAT	(devc->base+0x78)
+
+#define	TWO_WIRE_ADDR		(devc->base+0x90)
+#define	TWO_WIRE_MAP		(devc->base+0x91)
+#define	TWO_WIRE_DATA		(devc->base+0x92)
+#define	TWO_WIRE_CTRL		(devc->base+0x94)
+
+#define	SPI_CONTROL		(devc->base+0x98)
+#define	SPI_DATA		(devc->base+0x99)
+
+#define	MPU401_DATA		(devc->base+0xA0)
+#define	MPU401_COMMAND		(devc->base+0xA1)
+#define	MPU401_CONTROL		(devc->base+0xA2)
+
+#define	GPI_DATA		(devc->base+0xA4)
+#define	GPI_IRQ_MASK		(devc->base+0xA5)
+#define	GPIO_DATA		(devc->base+0xA6)
+#define	GPIO_CONTROL		(devc->base+0xA8)
+#define	GPIO_IRQ_MASK		(devc->base+0xAA)
+#define	DEVICE_SENSE		(devc->base+0xAC)
+
+#define	PLAY_ROUTING		(devc->base+0xC0)
+#define	REC_ROUTING		(devc->base+0xC2)
+#define	REC_MONITOR		(devc->base+0xC3)
+#define	MONITOR_ROUTING		(devc->base+0xC4)
+
+#define	AC97_CTRL		(devc->base+0xD0)
+#define	AC97_INTR_MASK		(devc->base+0xD2)
+#define	AC97_INTR_STAT		(devc->base+0xD3)
+#define	AC97_OUT_CHAN_CONFIG	(devc->base+0xD4)
+#define	AC97_IN_CHAN_CONFIG	(devc->base+0xD8)
+#define	AC97_CMD_DATA		(devc->base+0xDC)
+
+#define	CODEC_VERSION		(devc->base+0xE4)
+#define	CTRL_VERSION		(devc->base+0xE6)
+
+/* Device IDs */
+#define	ASUS_VENDOR_ID		0x1043
+#define	SUBID_XONAR_D2		0x8269
+#define	SUBID_XONAR_D2X		0x82b7
+#define	SUBID_XONAR_D1		0x834f
+#define	SUBID_XONAR_DX		0x8275
+#define	SUBID_XONAR_STX		0x835c
+#define	SUBID_XONAR_DS		0x838e
+
+
+#define	SUBID_GENERIC		0x0000
+
+/* Xonar specific */
+#define	XONAR_DX_FRONTDAC	0x9e
+#define	XONAR_DX_SURRDAC	0x30
+#define	XONAR_STX_FRONTDAC	0x98
+#define	XONAR_DS_FRONTDAC	0x1
+#define	XONAR_DS_SURRDAC	0x0
+
+/* defs for AKM 4396 DAC */
+#define	AK4396_CTL1		0x00
+#define	AK4396_CTL2		0x01
+#define	AK4396_CTL3		0x02
+#define	AK4396_LchATTCtl	0x03
+#define	AK4396_RchATTCtl	0x04
+
+/* defs for CS4398 DAC */
+#define	CS4398_CHIP_ID		0x01
+#define	CS4398_MODE_CTRL	0x02
+#define	CS4398_MIXING		0x03
+#define	CS4398_MUTE_CTRL	0x04
+#define	CS4398_VOLA		0x05
+#define	CS4398_VOLB		0x06
+#define	CS4398_RAMP_CTRL	0x07
+#define	CS4398_MISC_CTRL	0x08
+#define	CS4398_MISC2_CTRL	0x09
+#define	CS4398_POWER_DOWN	(1<<7)	/* Obvious */
+#define	CS4398_CPEN		(1<<6)  /* Control Port Enable */
+#define	CS4398_FREEZE		(1<<5)	/* Freezes registers, unfreeze to */
+					/* accept changed registers */
+#define	CS4398_MCLKDIV2		(1<<4)	/* Divide MCLK by 2 */
+#define	CS4398_MCLKDIV3		(1<<3)	/* Divive MCLK by 3 */
+#define	CS4398_I2S		(1<<4)	/* Set I2S mode */
+
+/* defs for CS4362A DAC */
+#define	CS4362A_MODE1_CTRL	0x01
+#define	CS4362A_MODE2_CTRL	0x02
+#define	CS4362A_MODE3_CTRL	0x03
+#define	CS4362A_FILTER_CTRL	0x04
+#define	CS4362A_INVERT_CTRL	0x05
+#define	CS4362A_MIX1_CTRL	0x06
+#define	CS4362A_VOLA_1		0x07
+#define	CS4362A_VOLB_1		0x08
+#define	CS4362A_MIX2_CTRL	0x09
+#define	CS4362A_VOLA_2		0x0A
+#define	CS4362A_VOLB_2		0x0B
+#define	CS4362A_MIX3_CTRL	0x0C
+#define	CS4362A_VOLA_3		0x0D
+#define	CS4362A_VOLB_3		0x0E
+#define	CS4362A_CHIP_REV	0x12
+
+/* CS4362A Reg 01h */
+#define	CS4362A_CPEN		(1<<7)
+#define	CS4362A_FREEZE		(1<<6)
+#define	CS4362A_MCLKDIV		(1<<5)
+#define	CS4362A_DAC3_ENABLE	(1<<3)
+#define	CS4362A_DAC2_ENABLE	(1<<2)
+#define	CS4362A_DAC1_ENABLE	(1<<1)
+#define	CS4362A_POWER_DOWN	(1)
+
+/* CS4362A Reg 02h */
+#define	CS4362A_DIF_LJUST	0x00
+#define	CS4362A_DIF_I2S		0x10
+#define	CS4362A_DIF_RJUST16	0x20
+#define	CS4362A_DIF_RJUST24	0x30
+#define	CS4362A_DIF_RJUST20	0x40
+#define	CS4362A_DIF_RJUST18	0x50
+
+/* CS4362A Reg 03h */
+#define	CS4362A_RAMP_IMMEDIATE	0x00
+#define	CS4362A_RAMP_ZEROCROSS	0x40
+#define	CS4362A_RAMP_SOFT	0x80
+#define	CS4362A_RAMP_SOFTZERO	0xC0
+#define	CS4362A_SINGLE_VOL	0x20
+#define	CS4362A_RAMP_ERROR	0x10
+#define	CS4362A_MUTEC_POL	0x08
+#define	CS4362A_AUTOMUTE	0x04
+#define	CS4362A_SIX_MUTE	0x00
+#define	CS4362A_ONE_MUTE	0x01
+#define	CS4362A_THREE_MUTE	0x03
+
+/* CS4362A Reg 04h */
+#define	CS4362A_FILT_SEL	0x10
+#define	CS4362A_DEM_NONE	0x00
+#define	CS4362A_DEM_44KHZ	0x02
+#define	CS4362A_DEM_48KHZ	0x04
+#define	CS4362A_DEM_32KHZ	0x06
+#define	CS4362A_RAMPDOWN	0x01
+
+
+/* CS4362A Reg 05h */
+#define	CS4362A_INV_A3		(1<<4)
+#define	CS4362A_INV_B3		(1<<5)
+#define	CS4362A_INV_A2		(1<<2)
+#define	CS4362A_INV_B2		(1<<3)
+#define	CS4362A_INV_A1		(1)
+#define	CS4362A_INV_B1		(1<<1)
+
+/* CS4362A Reg 06h, 09h, 0Ch */
+/* ATAPI crap, does anyone still use analog CD playback? */
+
+/* CS4362A Reg 07h, 08h, 0Ah, 0Bh, 0Dh, 0Eh */
+/* Volume registers */
+#define	CS4362A_VOL_MUTE	0x80
+
+/* 0-100. Start at -96dB. */
+#define	CS4398_VOL(x) \
+	((x) == 0 ? 0xFF : (0xC0 - ((x)*192/100)))
+/* 0-100. Start at -96dB. Bit 7 is mute. */
+#define	CS4362A_VOL(x) \
+	(char)((x) == 0 ? 0xFF : (0x60 - ((x)*96/100)))
+
+/* Xonar D2/D2X codec remap */
+static const char xd2_codec_map[4] = {
+	0, 1, 2, 4
+};
+
+
+typedef struct _cmediahd_devc_t cmediahd_devc_t;
+typedef struct _cmediahd_portc_t cmediahd_portc_t;
+
+typedef enum {
+	CTL_VOLUME = 0,
+	CTL_FRONT,
+	CTL_REAR,
+	CTL_CENTER,
+	CTL_LFE,
+	CTL_SURROUND,
+	CTL_MONITOR,
+	CTL_RECSRC,
+	CTL_RECGAIN,
+	CTL_MICVOL,
+	CTL_AUXVOL,
+	CTL_CDVOL,
+	CTL_LOOP,
+	CTL_SPREAD,
+	CTL_NUM		/* must be last */
+} cmediahd_ctrl_num_t;
+
+typedef struct cmediahd_ctrl
+{
+	cmediahd_devc_t		*devc;
+	audio_ctrl_t		*ctrl;
+	cmediahd_ctrl_num_t	num;
+	uint64_t		val;
+} cmediahd_ctrl_t;
+
+typedef struct cmediahd_regs
+{
+	caddr_t addr;	/* base address */
+	caddr_t size;	/* current count */
+	caddr_t frag;	/* terminal count */
+	caddr_t i2s;    /* i2s reg */
+	int chan; 	/* rec a/b/c, play spdif/multi/front */
+#define	REC_A 0
+#define	REC_B 1
+#define	REC_C 2
+#define	PLAY_SPDIF 3
+#define	PLAY_MULTI 4
+#define	PLAY_FRONT 5
+} cmediahd_regs_t;
+
+struct _cmediahd_portc_t
+{
+	cmediahd_devc_t *devc;
+	audio_engine_t *engine;
+
+	int			chans;
+	int			direction;
+
+	ddi_dma_handle_t	buf_dmah;	/* dma for buffers */
+	ddi_acc_handle_t	buf_acch;
+	uint32_t		paddr;
+	caddr_t			kaddr;
+	size_t			buf_size;
+	size_t			buf_frames;	/* Buffer size in frames */
+	unsigned		fragfr;
+	unsigned		nfrags;
+	unsigned		nframes;
+	unsigned		bufsz;
+	size_t			offset;
+	uint64_t		count;
+	int			syncdir;
+};
+
+struct _cmediahd_devc_t
+{
+	dev_info_t		*dip;
+	audio_dev_t		*adev;
+	boolean_t		has_ac97, has_fp_ac97;
+	int			model;
+	ac97_t			*ac97, *fp_ac97;
+
+	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 */
+	cmediahd_regs_t		rec_eng;	/* which rec engine to use */
+	cmediahd_portc_t 	*portc[CMEDIAHD_NUM_PORTC];
+	int			gpio_mic, gpio_out, gpio_codec, gpio_alt;
+	cmediahd_ctrl_t		controls[CTL_NUM];
+};
+
+#define	INB(devc, reg)		ddi_get8(devc->regsh, (void *)(reg))
+#define	OUTB(devc, val, reg)	ddi_put8(devc->regsh, (void *)(reg), (val))
+
+#define	INW(devc, reg)		ddi_get16(devc->regsh, (void *)(reg))
+#define	OUTW(devc, val, reg)	ddi_put16(devc->regsh, (void *)(reg), (val))
+
+#define	INL(devc, reg)		ddi_get32(devc->regsh, (void *)(reg))
+#define	OUTL(devc, val, reg)	ddi_put32(devc->regsh, (void *)(reg), (val))
+
+#endif /* CMEDIAHD_H */
--- a/usr/src/uts/intel/Makefile.intel.shared	Mon Jan 31 15:13:01 2011 -0800
+++ b/usr/src/uts/intel/Makefile.intel.shared	Mon Jan 31 17:40:15 2011 -0800
@@ -21,6 +21,8 @@
 
 # Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
 
+# Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
+
 #
 #	This makefile contains the common definitions for all intel
 #	implementation architecture independent modules.
@@ -195,6 +197,7 @@
 DRV_KMODS	+= audio1575
 DRV_KMODS	+= audio810
 DRV_KMODS	+= audiocmi
+DRV_KMODS	+= audiocmihd
 DRV_KMODS	+= audioemu10k
 DRV_KMODS	+= audioens
 DRV_KMODS	+= audiohd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/intel/audiocmihd/Makefile	Mon Jan 31 17:40:15 2011 -0800
@@ -0,0 +1,83 @@
+#
+# 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/audiocmihd/Makefile
+#
+# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+# Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
+#
+#	This makefile drives the production of the audiocmihd driver.
+#
+
+#
+#	Path to the base of the uts directory tree (usually /usr/src/uts).
+#
+UTSBASE = ../..
+
+#
+#	Define the module and object file sets.
+#
+MODULE		= audiocmihd
+OBJECTS		= $(AUDIOCMIHD_OBJS:%=$(OBJS_DIR)/%)
+LINTS		= $(AUDIOCMIHD_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