summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/io/audio
diff options
context:
space:
mode:
authorGarrett D'Amore <gdamore@opensolaris.org>2009-10-29 21:38:34 -0700
committerGarrett D'Amore <gdamore@opensolaris.org>2009-10-29 21:38:34 -0700
commit992413f4053d9470046876b234fe094062b730b7 (patch)
treeeac0eb4f9cbb450a0e672c468e8fde58d1a93979 /usr/src/uts/common/io/audio
parent55afc3a5dff2e96f329b696091ac9bd438a1db7e (diff)
downloadillumos-gate-992413f4053d9470046876b234fe094062b730b7.tar.gz
PSARC 2009/519 audioemu10k device driver
6539690 add sound driver for EMU10K chip
Diffstat (limited to 'usr/src/uts/common/io/audio')
-rw-r--r--usr/src/uts/common/io/audio/ac97/ac97.c16
-rw-r--r--usr/src/uts/common/io/audio/drv/audioemu10k/audioemu10k.c2616
-rw-r--r--usr/src/uts/common/io/audio/drv/audioemu10k/audioemu10k.h453
-rw-r--r--usr/src/uts/common/io/audio/drv/audioemu10k/dsp/asm10k.c1096
-rw-r--r--usr/src/uts/common/io/audio/drv/audioemu10k/dsp/emu10k.dsp73
-rw-r--r--usr/src/uts/common/io/audio/drv/audioemu10k/dsp/emu10k1.mac154
-rw-r--r--usr/src/uts/common/io/audio/drv/audioemu10k/dsp/emu10k2.mac159
7 files changed, 4560 insertions, 7 deletions
diff --git a/usr/src/uts/common/io/audio/ac97/ac97.c b/usr/src/uts/common/io/audio/ac97/ac97.c
index dc35e279c9..5cbc69b823 100644
--- a/usr/src/uts/common/io/audio/ac97/ac97.c
+++ b/usr/src/uts/common/io/audio/ac97/ac97.c
@@ -1217,12 +1217,6 @@ ac_hw_reset(ac97_t *ac)
WR(AC97_VENDOR_REGISTER_11, 8);
break;
- case AC97_CODEC_EM28028:
- ac_wr(ac, AC97_EXTENDED_AUDIO_STAT_CTRL_REGISTER,
- (ac_rd(ac, AC97_EXTENDED_AUDIO_STAT_CTRL_REGISTER) &
- ~3800) | 0xE0);
- break;
-
case AC97_CODEC_AD1886:
/* jack sense */
WR(AC97_VENDOR_REGISTER_13,
@@ -1259,7 +1253,7 @@ ac_hw_reset(ac97_t *ac)
case AC97_CODEC_VT1612A:
case AC97_CODEC_VT1617A:
case AC97_CODEC_VT1616:
- /* Turn off Center, Surround, and LFE DACs */
+ /* Turn on Center, Surround, and LFE DACs */
ac_clr(ac, AC97_EXTENDED_AUDIO_STAT_CTRL_REGISTER,
EASCR_PRI | EASCR_PRJ | EASCR_PRK);
WR(AC97_VENDOR_REGISTER_01, 0x0230);
@@ -1829,11 +1823,18 @@ static struct vendor {
{ AC97_VENDOR_CMI, "C-Media" },
{ AC97_VENDOR_CRY, "Cirrus Logic" },
{ AC97_VENDOR_CXT, "Conexant" },
+ { AC97_VENDOR_EMC, "eMicro" },
{ AC97_VENDOR_ESS, "ESS Technology" },
{ AC97_VENDOR_EV, "Ectiva" },
+ { AC97_VENDOR_HRS, "Intersil" },
{ AC97_VENDOR_ICE, "ICEnsemble" },
+ { AC97_VENDOR_ITE, "ITE, Inc." },
+ { AC97_VENDOR_NSC, "National Semiconductor" },
+ { AC97_VENDOR_PSC, "Philips Semiconductor" },
+ { AC97_VENDOR_SIL, "Silicon Laboratories" },
{ AC97_VENDOR_ST, "SigmaTel" },
{ AC97_VENDOR_TRA, "TriTech", },
+ { AC97_VENDOR_TXN, "Texas Instruments", },
{ AC97_VENDOR_VIA, "VIA Technologies" },
{ AC97_VENDOR_WML, "Wolfson" },
{ AC97_VENDOR_YMH, "Yamaha" },
@@ -1892,6 +1893,7 @@ static struct codec {
{ AC97_CODEC_WM9704, "WM9704" },
{ AC97_CODEC_ES1921, "ES1921" },
{ AC97_CODEC_ICE1232, "ICE1232/VT1611A" },
+ { AC97_CODEC_LM4550, "LM4550" },
{ AC97_CODEC_VT1612A, "VT1612A" },
{ AC97_CODEC_VT1616, "VT1616" },
{ AC97_CODEC_VT1616A, "VT1616A" },
diff --git a/usr/src/uts/common/io/audio/drv/audioemu10k/audioemu10k.c b/usr/src/uts/common/io/audio/drv/audioemu10k/audioemu10k.c
new file mode 100644
index 0000000000..d94d8c726a
--- /dev/null
+++ b/usr/src/uts/common/io/audio/drv/audioemu10k/audioemu10k.c
@@ -0,0 +1,2616 @@
+/*
+ * 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.
+ */
+
+/*
+ * Copyright (C) 4Front Technologies 1996-2009.
+ */
+
+/*
+ * Purpose: Driver for the Creative Sound Blaster Live! and Audigy/2/4
+ * sound cards
+ */
+
+#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/stdbool.h>
+#include <sys/audio/audio_driver.h>
+#include <sys/audio/ac97.h>
+
+#include "audioemu10k.h"
+#include <sys/promif.h>
+
+/*
+ * Include the DSP files for emu10k1 (Live!) and emu10k2 (Audigy)
+ */
+#include "emu10k_gpr.h"
+#include "emu10k1_dsp.h"
+#include "emu10k2_dsp.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
+};
+
+
+/*
+ * EMU10K routing stuff.
+ */
+#define MAX_SENDS 4
+#define SEND_L 0
+#define SEND_R 1
+#define SEND_SURRL 2
+#define SEND_SURRR 3
+#define SEND_CEN 4
+#define SEND_LFE 5
+#define SEND_SIDEL 6
+#define SEND_SIDER 7
+
+#define SPDIF_L 20
+#define SPDIF_R 21
+
+/*
+ * Recording sources... we start from 16 to ensure that the
+ * record sources don't collide with AC'97 record sources in
+ * the control value.
+ */
+#define INPUT_AC97 1
+#define INPUT_SPD1 2
+#define INPUT_SPD2 3
+#define INPUT_DIGCD 4
+#define INPUT_AUX2 5
+#define INPUT_LINE2 6
+#define INPUT_STEREOMIX 7
+
+static uint8_t front_routing[MAX_SENDS] = {
+ SEND_L, SEND_R, 0x3f, 0x3f
+};
+static uint8_t surr_routing[MAX_SENDS] = {
+ SEND_SURRL, SEND_SURRR, 0x3f, 0x3f
+};
+static uint8_t clfe_routing[MAX_SENDS] = {
+ SEND_CEN, SEND_LFE, 0x3f, 0x3f
+};
+static uint8_t side_routing[MAX_SENDS] = {
+ SEND_SIDEL, SEND_SIDER, 0x3f, 0x3f
+};
+static uint8_t no_routing[MAX_SENDS] = {
+ 0x3f, 0x3f, 0x3f, 0x3f
+};
+
+/*
+ * SB Live! cannot do DMA above 2G addresses. Audigy/2/4 have special 8k page
+ * mode that supports high addresses. However, we should not need this except
+ * on SPARC. For simplicity's sake, we are only delivering this driver for
+ * x86 platforms. If SPARC support is desired, then the code will have to
+ * be modified to support full 32-bit addressing. (And again, SB Live!
+ * can't do it anyway.)
+ */
+
+static ddi_dma_attr_t dma_attr_buf = {
+ DMA_ATTR_V0, /* Version */
+ 0x00000000ULL, /* Address low */
+ 0x7ffffff0ULL, /* Address high */
+ 0xffffffffULL, /* Counter max */
+ 1ULL, /* Default byte align */
+ 0x7f, /* Burst size */
+ 0x1, /* Minimum xfer size */
+ 0xffffffffULL, /* Maximum xfer size */
+ 0xffffffffULL, /* Max segment size */
+ 1, /* S/G list length */
+ 1, /* Granularity */
+ 0 /* Flag */
+};
+
+static int emu10k_attach(dev_info_t *);
+static int emu10k_resume(dev_info_t *);
+static int emu10k_detach(emu10k_devc_t *);
+static int emu10k_suspend(emu10k_devc_t *);
+
+static int emu10k_open(void *, int, unsigned *, unsigned *, caddr_t *);
+static void emu10k_close(void *);
+static int emu10k_start(void *);
+static void emu10k_stop(void *);
+static int emu10k_format(void *);
+static int emu10k_channels(void *);
+static int emu10k_rate(void *);
+static uint64_t emu10k_count(void *);
+static void emu10k_sync(void *, unsigned);
+static size_t emu10k_qlen(void *);
+static void emu10k_chinfo(void *, int, unsigned *, unsigned *);
+
+static uint16_t emu10k_read_ac97(void *, uint8_t);
+static void emu10k_write_ac97(void *, uint8_t, uint16_t);
+static int emu10k_alloc_port(emu10k_devc_t *, int);
+static void emu10k_destroy(emu10k_devc_t *);
+static int emu10k_setup_intrs(emu10k_devc_t *);
+static int emu10k_hwinit(emu10k_devc_t *);
+static uint_t emu10k_intr(caddr_t, caddr_t);
+static void emu10k_init_effects(emu10k_devc_t *);
+
+static audio_engine_ops_t emu10k_engine_ops = {
+ AUDIO_ENGINE_VERSION,
+ emu10k_open,
+ emu10k_close,
+ emu10k_start,
+ emu10k_stop,
+ emu10k_count,
+ emu10k_format,
+ emu10k_channels,
+ emu10k_rate,
+ emu10k_sync,
+ emu10k_qlen,
+ emu10k_chinfo
+};
+
+static uint16_t
+emu10k_read_ac97(void *arg, uint8_t index)
+{
+ emu10k_devc_t *devc = arg;
+ int dtemp = 0, i;
+
+ mutex_enter(&devc->mutex);
+ OUTB(devc, index, devc->regs + 0x1e);
+ for (i = 0; i < 10000; i++)
+ if (INB(devc, devc->regs + 0x1e) & 0x80)
+ break;
+
+ if (i == 1000) {
+ mutex_exit(&devc->mutex);
+ return (0); /* Timeout */
+ }
+ dtemp = INW(devc, devc->regs + 0x1c);
+
+ mutex_exit(&devc->mutex);
+
+ return (dtemp & 0xffff);
+}
+
+static void
+emu10k_write_ac97(void *arg, uint8_t index, uint16_t data)
+{
+ emu10k_devc_t *devc = arg;
+ int i;
+
+ mutex_enter(&devc->mutex);
+
+ OUTB(devc, index, devc->regs + 0x1e);
+ for (i = 0; i < 10000; i++)
+ if (INB(devc, devc->regs + 0x1e) & 0x80)
+ break;
+ OUTW(devc, data, devc->regs + 0x1c);
+
+ mutex_exit(&devc->mutex);
+}
+
+static uint32_t
+emu10k_read_reg(emu10k_devc_t *devc, int reg, int chn)
+{
+ uint32_t ptr, ptr_addr_mask, val, mask, size, offset;
+
+ ptr_addr_mask = (devc->feature_mask &
+ (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL)) ?
+ 0x0fff0000 : 0x07ff0000;
+ ptr = ((reg << 16) & ptr_addr_mask) | (chn & 0x3f);
+ OUTL(devc, ptr, devc->regs + 0x00); /* Pointer */
+ val = INL(devc, devc->regs + 0x04); /* Data */
+ if (reg & 0xff000000) {
+ size = (reg >> 24) & 0x3f;
+ offset = (reg >> 16) & 0x1f;
+ mask = ((1 << size) - 1) << offset;
+ val &= mask;
+ val >>= offset;
+ }
+
+ return (val);
+}
+
+static void
+emu10k_write_reg(emu10k_devc_t *devc, int reg, int chn, uint32_t value)
+{
+ uint32_t ptr, ptr_addr_mask, mask, size, offset;
+
+ ptr_addr_mask = (devc->feature_mask &
+ (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL)) ?
+ 0x0fff0000 : 0x07ff0000;
+ ptr = ((reg << 16) & ptr_addr_mask) | (chn & 0x3f);
+ OUTL(devc, ptr, devc->regs + 0x00); /* Pointer */
+ if (reg & 0xff000000) {
+ size = (reg >> 24) & 0x3f;
+ offset = (reg >> 16) & 0x1f;
+ mask = ((1 << size) - 1) << offset;
+ value <<= offset;
+ value &= mask;
+ value |= INL(devc, devc->regs + 0x04) & ~mask; /* data */
+ }
+ OUTL(devc, value, devc->regs + 0x04); /* Data */
+}
+
+static void
+emu10k_write_routing(emu10k_devc_t *devc, int voice, unsigned char *routing)
+{
+ int i;
+
+ ASSERT(routing != NULL);
+
+ if (devc->feature_mask & (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL)) {
+ unsigned int srda = 0;
+
+ for (i = 0; i < 4; i++)
+ srda |= routing[i] << (i * 8);
+
+ emu10k_write_reg(devc, SRDA, voice, srda);
+ } else {
+ int fxrt = 0;
+
+ for (i = 0; i < 4; i++)
+ fxrt |= routing[i] << ((i * 4) + 16);
+ emu10k_write_reg(devc, FXRT, voice, fxrt);
+ }
+}
+
+static void
+emu10k_write_efx(emu10k_devc_t *devc, int reg, unsigned int value)
+{
+ emu10k_write_reg(devc, reg, 0, value);
+}
+
+static uint_t
+emu10k_intr(caddr_t argp, caddr_t nocare)
+{
+ emu10k_devc_t *devc = (void *) argp;
+ emu10k_portc_t *portc;
+ uint32_t status;
+ audio_engine_t *cons = NULL, *prod = NULL;
+
+
+ _NOTE(ARGUNUSED (nocare));
+
+ mutex_enter(&devc->mutex);
+ if (devc->suspended) {
+ mutex_exit(&devc->mutex);
+ return (DDI_INTR_UNCLAIMED);
+ }
+
+ status = INL(devc, devc->regs + INTPEND);
+
+ if (status == 0) {
+ mutex_exit(&devc->mutex);
+ return (DDI_INTR_UNCLAIMED);
+ }
+
+ if (status & INT_CL) { /* channel loop */
+ emu10k_write_reg(devc, CLIPL, 0, (1U << 8));
+ OUTL(devc, INT_CL, devc->regs + INTPEND);
+ portc = devc->portc[EMU10K_PLAY];
+ if (portc->active) {
+ cons = portc->engine;
+ }
+ }
+ if (status & (INT_AF|INT_AH|INT_MF|INT_MH)) { /* ADC interrupt */
+ OUTL(devc, INT_AF|INT_AH|INT_MF|INT_MH, devc->regs + INTPEND);
+ portc = devc->portc[EMU10K_REC];
+ if (portc->active) {
+ prod = portc->engine;
+ }
+ }
+
+ mutex_exit(&devc->mutex);
+
+ if (cons) {
+ audio_engine_consume(cons);
+ }
+ if (prod) {
+ audio_engine_produce(prod);
+ }
+
+ return (DDI_INTR_CLAIMED);
+}
+
+/*
+ * Audio routines
+ */
+
+static void
+emu10k_update_output_volume(emu10k_portc_t *portc, int voice, int chn)
+{
+ emu10k_devc_t *devc = portc->devc;
+ unsigned int tmp;
+ unsigned char send[2];
+
+ /*
+ * Each voice operator of EMU10k has 4 sends (0=left, 1=right,
+ * 2=surround_left, 3=surround_right). The original OSS driver
+ * used all of them to spread stereo output to two different
+ * speaker pairs. This Boomer version uses only the first two
+ * sends. The other sends are set to 0.
+ *
+ * Boomer uses multiple voice pairs to play multichannel
+ * audio. This function is used to update only one of these
+ * pairs.
+ */
+
+ send[0] = 0xff; /* Max */
+ send[1] = 0xff; /* Max */
+
+ /* Analog voice */
+ if (chn == LEFT_CH) {
+ send[1] = 0;
+ } else {
+ send[0] = 0;
+ }
+
+ tmp = emu10k_read_reg(devc, PTAB, voice) & 0xffff0000;
+ emu10k_write_reg(devc, PTAB, voice, tmp | (send[0] << 8) | send[1]);
+}
+
+static void
+emu10k_setup_voice(emu10k_portc_t *portc, int voice, int chn, int buf_offset)
+{
+ emu10k_devc_t *devc = portc->devc;
+ unsigned int nCRA = 0;
+
+ unsigned int loop_start, loop_end, buf_size;
+
+ int sz;
+ int start_pos;
+
+ emu10k_write_reg(devc, VEDS, voice, 0x0); /* OFF */
+ emu10k_write_reg(devc, VTFT, voice, 0xffff);
+ emu10k_write_reg(devc, CVCF, voice, 0xffff);
+
+ sz = 2; /* Shift value for 16 bits stereo */
+
+ /* Size of one stereo sub buffer */
+ buf_size = (portc->buf_size / portc->channels) * 2;
+ loop_start = (portc->memptr + buf_offset) >> sz;
+ loop_end = (portc->memptr + buf_offset + buf_size) >> sz;
+
+ /* set stereo */
+ emu10k_write_reg(devc, CPF, voice, 0x8000);
+
+ nCRA = 28; /* Stereo (16 bits) */
+ start_pos = loop_start + nCRA;
+
+ /* SDL, ST, CA */
+
+ emu10k_write_reg(devc, SDL, voice, loop_end);
+ emu10k_write_reg(devc, SCSA, voice, loop_start);
+ emu10k_write_reg(devc, PTAB, voice, 0);
+
+ emu10k_update_output_volume(portc, voice, chn); /* Set volume */
+
+ emu10k_write_reg(devc, QKBCA, voice, start_pos);
+
+ emu10k_write_reg(devc, Z1, voice, 0);
+ emu10k_write_reg(devc, Z2, voice, 0);
+
+ /* This is really a physical address */
+ emu10k_write_reg(devc, MAPA, voice,
+ 0x1fff | (devc->silence_paddr << 1));
+ emu10k_write_reg(devc, MAPB, voice,
+ 0x1fff | (devc->silence_paddr << 1));
+
+ emu10k_write_reg(devc, VTFT, voice, 0x0000ffff);
+ emu10k_write_reg(devc, CVCF, voice, 0x0000ffff);
+ emu10k_write_reg(devc, MEHA, voice, 0);
+ emu10k_write_reg(devc, MEDS, voice, 0x7f);
+ emu10k_write_reg(devc, MLV, voice, 0x8000);
+ emu10k_write_reg(devc, VLV, voice, 0x8000);
+ emu10k_write_reg(devc, VFM, voice, 0);
+ emu10k_write_reg(devc, TMFQ, voice, 0);
+ emu10k_write_reg(devc, VVFQ, voice, 0);
+ emu10k_write_reg(devc, MEV, voice, 0x8000);
+ emu10k_write_reg(devc, VEHA, voice, 0x7f7f); /* OK */
+ /* No volume envelope delay (OK) */
+ emu10k_write_reg(devc, VEV, voice, 0x8000);
+ emu10k_write_reg(devc, PEFE_FILTERAMOUNT, voice, 0x7f);
+ emu10k_write_reg(devc, PEFE_PITCHAMOUNT, voice, 0x00);
+}
+
+static void
+emu10k_setup_silence(emu10k_portc_t *portc, int voice)
+{
+ emu10k_devc_t *devc = portc->devc;
+
+ emu10k_write_reg(devc, VEDS, voice, 0x0); /* OFF */
+ emu10k_write_reg(devc, VTFT, voice, 0xffff);
+ emu10k_write_reg(devc, CVCF, voice, 0xffff);
+
+ /* set stereo */
+ emu10k_write_reg(devc, CPF, voice, 0x8000);
+
+ /* SDL, ST, CA */
+ emu10k_write_reg(devc, SDL, voice, portc->fragfr);
+ emu10k_write_reg(devc, SCSA, voice, 0);
+ emu10k_write_reg(devc, PTAB, voice, 0);
+ emu10k_write_reg(devc, QKBCA, voice, 0);
+
+ emu10k_write_reg(devc, Z1, voice, 0);
+ emu10k_write_reg(devc, Z2, voice, 0);
+
+ /* This is really a physical address */
+ emu10k_write_reg(devc, MAPA, voice,
+ 0x1fff | (devc->silence_paddr << 1));
+ emu10k_write_reg(devc, MAPB, voice,
+ 0x1fff | (devc->silence_paddr << 1));
+
+ emu10k_write_reg(devc, VTFT, voice, 0x0000ffff);
+ emu10k_write_reg(devc, CVCF, voice, 0x0000ffff);
+ emu10k_write_reg(devc, MEHA, voice, 0);
+ emu10k_write_reg(devc, MEDS, voice, 0x7f);
+ emu10k_write_reg(devc, MLV, voice, 0x8000);
+ emu10k_write_reg(devc, VLV, voice, 0x8000);
+ emu10k_write_reg(devc, VFM, voice, 0);
+ emu10k_write_reg(devc, TMFQ, voice, 0);
+ emu10k_write_reg(devc, VVFQ, voice, 0);
+ emu10k_write_reg(devc, MEV, voice, 0x8000);
+ emu10k_write_reg(devc, VEHA, voice, 0x7f7f); /* OK */
+ /* No volume envelope delay (OK) */
+ emu10k_write_reg(devc, VEV, voice, 0x8000);
+ emu10k_write_reg(devc, PEFE_FILTERAMOUNT, voice, 0x7f);
+ emu10k_write_reg(devc, PEFE_PITCHAMOUNT, voice, 0x00);
+}
+
+int
+emu10k_open(void *arg, int flag,
+ unsigned *fragfrp, unsigned *nfragsp, caddr_t *bufp)
+{
+ emu10k_portc_t *portc = arg;
+ emu10k_devc_t *devc = portc->devc;
+
+ _NOTE(ARGUNUSED(flag));
+
+ portc->started = B_FALSE;
+ portc->active = B_FALSE;
+ *fragfrp = portc->fragfr;
+ *nfragsp = portc->nfrags;
+ *bufp = portc->buf_kaddr;
+
+ mutex_enter(&devc->mutex);
+ if (!devc->suspended)
+ portc->reset_port(portc);
+ portc->count = 0;
+ mutex_exit(&devc->mutex);
+
+ return (0);
+}
+
+void
+emu10k_close(void *arg)
+{
+ emu10k_portc_t *portc = arg;
+ emu10k_devc_t *devc = portc->devc;
+
+ mutex_enter(&devc->mutex);
+ if (!devc->suspended)
+ portc->stop_port(portc);
+ portc->started = B_FALSE;
+ mutex_exit(&devc->mutex);
+}
+
+int
+emu10k_start(void *arg)
+{
+ emu10k_portc_t *portc = arg;
+ emu10k_devc_t *devc = portc->devc;
+
+ mutex_enter(&devc->mutex);
+ if (!portc->started) {
+ if (!devc->suspended)
+ portc->start_port(portc);
+ portc->started = B_TRUE;
+ }
+ mutex_exit(&devc->mutex);
+ return (0);
+}
+
+void
+emu10k_stop(void *arg)
+{
+ emu10k_portc_t *portc = arg;
+ emu10k_devc_t *devc = portc->devc;
+
+ mutex_enter(&devc->mutex);
+ if (portc->started) {
+ if (!devc->suspended)
+ portc->stop_port(portc);
+ portc->started = B_FALSE;
+ }
+ mutex_exit(&devc->mutex);
+}
+
+int
+emu10k_format(void *arg)
+{
+ _NOTE(ARGUNUSED(arg));
+
+ return (AUDIO_FORMAT_S16_LE);
+}
+
+int
+emu10k_channels(void *arg)
+{
+ emu10k_portc_t *portc = arg;
+
+ return (portc->channels);
+}
+
+int
+emu10k_rate(void *arg)
+{
+ _NOTE(ARGUNUSED(arg));
+
+ return (SAMPLE_RATE);
+}
+
+void
+emu10k_sync(void *arg, unsigned nframes)
+{
+ emu10k_portc_t *portc = arg;
+ _NOTE(ARGUNUSED(nframes));
+
+ (void) ddi_dma_sync(portc->buf_dmah, 0, 0, portc->syncdir);
+}
+
+size_t
+emu10k_qlen(void *arg)
+{
+ _NOTE(ARGUNUSED (arg));
+ return (0);
+}
+
+uint64_t
+emu10k_count(void *arg)
+{
+ emu10k_portc_t *portc = arg;
+ emu10k_devc_t *devc = portc->devc;
+ uint64_t count;
+
+ mutex_enter(&devc->mutex);
+ if (!devc->suspended)
+ portc->update_port(portc);
+ count = portc->count;
+ mutex_exit(&devc->mutex);
+
+ return (count);
+}
+
+static void
+emu10k_chinfo(void *arg, int chan, unsigned *offset, unsigned *incr)
+{
+ emu10k_portc_t *portc = arg;
+
+ *offset = portc->nframes * (chan / 2) * 2 + (chan % 2);
+ *incr = 2;
+}
+
+/* private implementation bits */
+
+static void
+emu10k_set_loop_stop(emu10k_devc_t *devc, int voice, int s)
+{
+ unsigned int tmp;
+ int offs, bit;
+
+ offs = voice / 32;
+ bit = voice % 32;
+ s = !!s;
+
+ tmp = emu10k_read_reg(devc, SOLL + offs, 0);
+ tmp &= ~(1 << bit);
+
+ if (s)
+ tmp |= (1 << bit);
+ emu10k_write_reg(devc, SOLL + offs, 0, tmp);
+}
+
+static unsigned int
+emu10k_rate_to_pitch(unsigned int rate)
+{
+ static unsigned int logMagTable[128] = {
+ 0x00000, 0x02dfc, 0x05b9e, 0x088e6,
+ 0x0b5d6, 0x0e26f, 0x10eb3, 0x13aa2,
+ 0x1663f, 0x1918a, 0x1bc84, 0x1e72e,
+ 0x2118b, 0x23b9a, 0x2655d, 0x28ed5,
+ 0x2b803, 0x2e0e8, 0x30985, 0x331db,
+ 0x359eb, 0x381b6, 0x3a93d, 0x3d081,
+ 0x3f782, 0x41e42, 0x444c1, 0x46b01,
+ 0x49101, 0x4b6c4, 0x4dc49, 0x50191,
+ 0x5269e, 0x54b6f, 0x57006, 0x59463,
+ 0x5b888, 0x5dc74, 0x60029, 0x623a7,
+ 0x646ee, 0x66a00, 0x68cdd, 0x6af86,
+ 0x6d1fa, 0x6f43c, 0x7164b, 0x73829,
+ 0x759d4, 0x77b4f, 0x79c9a, 0x7bdb5,
+ 0x7dea1, 0x7ff5e, 0x81fed, 0x8404e,
+ 0x86082, 0x88089, 0x8a064, 0x8c014,
+ 0x8df98, 0x8fef1, 0x91e20, 0x93d26,
+ 0x95c01, 0x97ab4, 0x9993e, 0x9b79f,
+ 0x9d5d9, 0x9f3ec, 0xa11d8, 0xa2f9d,
+ 0xa4d3c, 0xa6ab5, 0xa8808, 0xaa537,
+ 0xac241, 0xadf26, 0xafbe7, 0xb1885,
+ 0xb3500, 0xb5157, 0xb6d8c, 0xb899f,
+ 0xba58f, 0xbc15e, 0xbdd0c, 0xbf899,
+ 0xc1404, 0xc2f50, 0xc4a7b, 0xc6587,
+ 0xc8073, 0xc9b3f, 0xcb5ed, 0xcd07c,
+ 0xceaec, 0xd053f, 0xd1f73, 0xd398a,
+ 0xd5384, 0xd6d60, 0xd8720, 0xda0c3,
+ 0xdba4a, 0xdd3b4, 0xded03, 0xe0636,
+ 0xe1f4e, 0xe384a, 0xe512c, 0xe69f3,
+ 0xe829f, 0xe9b31, 0xeb3a9, 0xecc08,
+ 0xee44c, 0xefc78, 0xf148a, 0xf2c83,
+ 0xf4463, 0xf5c2a, 0xf73da, 0xf8b71,
+ 0xfa2f0, 0xfba57, 0xfd1a7, 0xfe8df
+ };
+ static char logSlopeTable[128] = {
+ 0x5c, 0x5c, 0x5b, 0x5a, 0x5a, 0x59, 0x58, 0x58,
+ 0x57, 0x56, 0x56, 0x55, 0x55, 0x54, 0x53, 0x53,
+ 0x52, 0x52, 0x51, 0x51, 0x50, 0x50, 0x4f, 0x4f,
+ 0x4e, 0x4d, 0x4d, 0x4d, 0x4c, 0x4c, 0x4b, 0x4b,
+ 0x4a, 0x4a, 0x49, 0x49, 0x48, 0x48, 0x47, 0x47,
+ 0x47, 0x46, 0x46, 0x45, 0x45, 0x45, 0x44, 0x44,
+ 0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x41, 0x41,
+ 0x41, 0x40, 0x40, 0x40, 0x3f, 0x3f, 0x3f, 0x3e,
+ 0x3e, 0x3e, 0x3d, 0x3d, 0x3d, 0x3c, 0x3c, 0x3c,
+ 0x3b, 0x3b, 0x3b, 0x3b, 0x3a, 0x3a, 0x3a, 0x39,
+ 0x39, 0x39, 0x39, 0x38, 0x38, 0x38, 0x38, 0x37,
+ 0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x35,
+ 0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x34,
+ 0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32,
+ 0x32, 0x31, 0x31, 0x31, 0x31, 0x31, 0x30, 0x30,
+ 0x30, 0x30, 0x30, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f
+ };
+ int i;
+
+ if (rate == 0)
+ return (0); /* Bail out if no leading "1" */
+ rate *= 11185; /* Scale 48000 to 0x20002380 */
+ for (i = 31; i > 0; i--) {
+ if (rate & 0x80000000) { /* Detect leading "1" */
+ return (((unsigned int) (i - 15) << 20) +
+ logMagTable[0x7f & (rate >> 24)] +
+ (0x7f & (rate >> 17)) *
+ logSlopeTable[0x7f & (rate >> 24)]);
+ }
+ rate <<= 1;
+ }
+
+ return (0); /* Should never reach this point */
+}
+
+static unsigned int
+emu10k_rate_to_linearpitch(unsigned int rate)
+{
+ rate = (rate << 8) / 375;
+ return (rate >> 1) + (rate & 1);
+}
+
+static void
+emu10k_prepare_voice(emu10k_devc_t *devc, int voice)
+{
+ unsigned int sample, initial_pitch, pitch_target;
+ unsigned int cra, cs, ccis, i;
+
+ /* setup CCR regs */
+ cra = 64;
+ cs = 4; /* Stereo */
+ ccis = 28; /* Stereo */
+ sample = 0; /* 16 bit silence */
+
+ for (i = 0; i < cs; i++)
+ emu10k_write_reg(devc, CD0 + i, voice, sample);
+
+ emu10k_write_reg(devc, CCR_CACHEINVALIDSIZE, voice, 0);
+ emu10k_write_reg(devc, CCR_READADDRESS, voice, cra);
+ emu10k_write_reg(devc, CCR_CACHEINVALIDSIZE, voice, ccis);
+
+ /* Set current pitch */
+ emu10k_write_reg(devc, IFA, voice, 0xff00);
+ emu10k_write_reg(devc, VTFT, voice, 0xffffffff);
+ emu10k_write_reg(devc, CVCF, voice, 0xffffffff);
+ emu10k_set_loop_stop(devc, voice, 0);
+
+ pitch_target = emu10k_rate_to_linearpitch(SAMPLE_RATE);
+ initial_pitch = emu10k_rate_to_pitch(SAMPLE_RATE) >> 8;
+ emu10k_write_reg(devc, PTRX_PITCHTARGET, voice, pitch_target);
+ emu10k_write_reg(devc, CPF_CURRENTPITCH, voice, pitch_target);
+ emu10k_write_reg(devc, IP, voice, initial_pitch);
+}
+
+static void
+emu10k_stop_voice(emu10k_devc_t *devc, int voice)
+{
+ emu10k_write_reg(devc, IFA, voice, 0xffff);
+ emu10k_write_reg(devc, VTFT, voice, 0xffff);
+ emu10k_write_reg(devc, PTRX_PITCHTARGET, voice, 0);
+ emu10k_write_reg(devc, CPF_CURRENTPITCH, voice, 0);
+ emu10k_write_reg(devc, IP, voice, 0);
+ emu10k_set_loop_stop(devc, voice, 1);
+}
+
+static void
+emu10k_reset_pair(emu10k_portc_t *portc, int voice, uint8_t *routing,
+ int buf_offset)
+{
+ emu10k_devc_t *devc = portc->devc;
+
+ /* Left channel */
+ /* Intial filter cutoff and attenuation */
+ emu10k_write_reg(devc, IFA, voice, 0xffff);
+ /* Volume envelope decay and sustain */
+ emu10k_write_reg(devc, VEDS, voice, 0x0);
+ /* Volume target and Filter cutoff target */
+ emu10k_write_reg(devc, VTFT, voice, 0xffff);
+ /* Pitch target and sends A and B */
+ emu10k_write_reg(devc, PTAB, voice, 0x0);
+
+ /* The same for right channel */
+ emu10k_write_reg(devc, IFA, voice + 1, 0xffff);
+ emu10k_write_reg(devc, VEDS, voice + 1, 0x0);
+ emu10k_write_reg(devc, VTFT, voice + 1, 0xffff);
+ emu10k_write_reg(devc, PTAB, voice + 1, 0x0);
+
+ /* now setup the voices and go! */
+ emu10k_setup_voice(portc, voice, LEFT_CH, buf_offset);
+ emu10k_setup_voice(portc, voice + 1, RIGHT_CH, buf_offset);
+
+ emu10k_write_routing(devc, voice, routing);
+ emu10k_write_routing(devc, voice + 1, routing);
+}
+
+void
+emu10k_start_play(emu10k_portc_t *portc)
+{
+ emu10k_devc_t *devc = portc->devc;
+
+ ASSERT(mutex_owned(&devc->mutex));
+ emu10k_prepare_voice(devc, 0);
+ emu10k_prepare_voice(devc, 1);
+
+ emu10k_prepare_voice(devc, 2);
+ emu10k_prepare_voice(devc, 3);
+
+ emu10k_prepare_voice(devc, 4);
+ emu10k_prepare_voice(devc, 5);
+
+ emu10k_prepare_voice(devc, 6);
+ emu10k_prepare_voice(devc, 7);
+
+ emu10k_prepare_voice(devc, 8);
+ emu10k_prepare_voice(devc, 9);
+
+ /* arrange to receive full loop interrupts on channel 8 */
+ emu10k_write_reg(devc, CLIEL, 0, (1U << 8));
+
+ /* initialize our position counter... */
+ portc->pos =
+ (emu10k_read_reg(devc, QKBCA, 0) & 0xffffff) -
+ (portc->memptr >> 2);
+
+ /* Trigger playback on all voices */
+ emu10k_write_reg(devc, VEDS, 0, 0x7f7f);
+ emu10k_write_reg(devc, VEDS, 1, 0x7f7f);
+ emu10k_write_reg(devc, VEDS, 2, 0x7f7f);
+ emu10k_write_reg(devc, VEDS, 3, 0x7f7f);
+ emu10k_write_reg(devc, VEDS, 4, 0x7f7f);
+ emu10k_write_reg(devc, VEDS, 5, 0x7f7f);
+ emu10k_write_reg(devc, VEDS, 6, 0x7f7f);
+ emu10k_write_reg(devc, VEDS, 7, 0x7f7f);
+ emu10k_write_reg(devc, VEDS, 8, 0x7f7f);
+ emu10k_write_reg(devc, VEDS, 9, 0x7f7f);
+
+ portc->active = B_TRUE;
+}
+
+void
+emu10k_stop_play(emu10k_portc_t *portc)
+{
+ emu10k_devc_t *devc = portc->devc;
+
+ emu10k_stop_voice(devc, 0);
+ emu10k_stop_voice(devc, 1);
+ emu10k_stop_voice(devc, 2);
+ emu10k_stop_voice(devc, 3);
+ emu10k_stop_voice(devc, 4);
+ emu10k_stop_voice(devc, 5);
+ emu10k_stop_voice(devc, 6);
+ emu10k_stop_voice(devc, 7);
+ emu10k_stop_voice(devc, 8);
+ emu10k_stop_voice(devc, 9);
+
+ portc->active = B_FALSE;
+}
+
+void
+emu10k_reset_play(emu10k_portc_t *portc)
+{
+ emu10k_devc_t *devc = portc->devc;
+ uint32_t offs;
+
+ offs = (portc->buf_size / portc->channels) * 2;
+
+ if (devc->feature_mask & SB_71) {
+ emu10k_reset_pair(portc, 0, front_routing, 0);
+ emu10k_reset_pair(portc, 2, clfe_routing, offs);
+ emu10k_reset_pair(portc, 4, surr_routing, 2 * offs);
+ emu10k_reset_pair(portc, 6, side_routing, 3 * offs);
+ } else if (devc->feature_mask & SB_51) {
+ emu10k_reset_pair(portc, 0, front_routing, 0);
+ emu10k_reset_pair(portc, 2, clfe_routing, offs);
+ emu10k_reset_pair(portc, 4, surr_routing, 2 * offs);
+ } else {
+ emu10k_reset_pair(portc, 0, front_routing, 0);
+ emu10k_reset_pair(portc, 2, surr_routing, offs);
+ }
+ emu10k_setup_silence(portc, 8);
+ emu10k_setup_silence(portc, 9);
+
+ /*
+ * This way we can use voices 8 and 9 for timing, we have
+ * programmed them to be just the size of a single fragment,
+ * that way when they loop we get a clean interrupt.
+ */
+ emu10k_write_routing(devc, 8, no_routing);
+ emu10k_write_routing(devc, 9, no_routing);
+ portc->pos = 0;
+}
+
+uint32_t emu10k_vars[5];
+
+void
+emu10k_update_play(emu10k_portc_t *portc)
+{
+ emu10k_devc_t *devc = portc->devc;
+ uint32_t cnt, pos;
+
+ /*
+ * Note: position is given as stereo samples, i.e. frames.
+ */
+ pos = emu10k_read_reg(devc, QKBCA, 0) & 0xffffff;
+ pos -= (portc->memptr >> 2);
+
+ if (pos <= portc->pos) {
+ cnt = portc->nframes - portc->pos;
+ cnt += pos;
+ } else {
+ cnt = (pos - portc->pos);
+ }
+ if (portc->dopos) {
+ emu10k_vars[0] = portc->pos;
+ emu10k_vars[1] = pos;
+ emu10k_vars[2] = (uint32_t)portc->count;
+ emu10k_vars[3] = cnt;
+ portc->dopos = 0;
+ }
+ if (cnt > portc->nframes) {
+ printf("Got bogus count %u\n", cnt);
+ cnt = portc->fragfr;
+ }
+ ASSERT(cnt <= portc->nframes);
+ portc->count += cnt;
+ portc->pos = pos;
+}
+
+void
+emu10k_start_rec(emu10k_portc_t *portc)
+{
+ emu10k_devc_t *devc = portc->devc;
+ uint32_t tmp;
+
+ /* Intr enable */
+ OUTL(devc, INL(devc, devc->regs + IE) | IE_MB | IE_AB,
+ devc->regs + IE);
+
+ tmp = 0; /* setup 48Kz */
+ if (devc->feature_mask & (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL))
+ tmp |= 0x30; /* Left/right channel enable */
+ else
+ tmp |= 0x18; /* Left/right channel enable */
+ emu10k_write_reg(devc, ADCSR, 0, tmp); /* GO */
+
+ portc->active = B_TRUE;
+}
+
+void
+emu10k_stop_rec(emu10k_portc_t *portc)
+{
+ emu10k_devc_t *devc = portc->devc;
+
+ ASSERT(mutex_owned(&devc->mutex));
+ emu10k_write_reg(devc, ADCSR, 0, 0);
+
+ portc->active = B_FALSE;
+}
+void
+emu10k_reset_rec(emu10k_portc_t *portc)
+{
+ emu10k_devc_t *devc = portc->devc;
+ uint32_t sz;
+
+ switch (portc->buf_size) {
+ case 4096:
+ sz = 15;
+ break;
+ case 8192:
+ sz = 19;
+ break;
+ case 16384:
+ sz = 23;
+ break;
+ case 32768:
+ sz = 27;
+ break;
+ case 65536:
+ sz = 31;
+ break;
+ }
+ emu10k_write_reg(devc, ADCBA, 0, portc->buf_paddr);
+ emu10k_write_reg(devc, ADCBS, 0, sz);
+ emu10k_write_reg(devc, ADCSR, 0, 0); /* reset for phase */
+ portc->pos = 0;
+ OUTL(devc, INL(devc, devc->regs + IE) & ~(IE_MB | IE_AB),
+ devc->regs + IE);
+}
+
+void
+emu10k_update_rec(emu10k_portc_t *portc)
+{
+ emu10k_devc_t *devc = portc->devc;
+ uint32_t cnt, pos;
+
+ /* given in bytes, we divide all counts by 4 to get samples */
+ pos = emu10k_read_reg(devc,
+ (devc->feature_mask & SB_LIVE) ? MIDX : ADCIDX, 0);
+ if (pos <= portc->pos) {
+ cnt = ((portc->buf_size) - portc->pos) >> 2;
+ cnt += (pos >> 2);
+ } else {
+ cnt = ((pos - portc->pos) >> 2);
+ }
+ portc->count += cnt;
+ portc->pos = pos;
+}
+
+int
+emu10k_alloc_port(emu10k_devc_t *devc, int num)
+{
+ emu10k_portc_t *portc;
+ size_t len;
+ ddi_dma_cookie_t cookie;
+ uint_t count;
+ int dir;
+ unsigned caps;
+ audio_dev_t *adev;
+ int i, n;
+
+ adev = devc->adev;
+ portc = kmem_zalloc(sizeof (*portc), KM_SLEEP);
+ devc->portc[num] = portc;
+ portc->devc = devc;
+ portc->started = B_FALSE;
+
+ portc->memptr = devc->audio_memptr;
+ devc->audio_memptr += (DMABUF_SIZE + 4095) & ~4095;
+
+ switch (num) {
+ case EMU10K_REC:
+ portc->syncdir = DDI_DMA_SYNC_FORKERNEL;
+ caps = ENGINE_INPUT_CAP;
+ dir = DDI_DMA_READ;
+ portc->channels = 2;
+ portc->start_port = emu10k_start_rec;
+ portc->stop_port = emu10k_stop_rec;
+ portc->reset_port = emu10k_reset_rec;
+ portc->update_port = emu10k_update_rec;
+ /* This is the minimum record buffer size. */
+ portc->buf_size = 4096;
+ portc->nfrags = 2;
+ portc->nframes = 4096 / 4;
+ portc->fragfr = portc->nframes / portc->nfrags;
+ break;
+ case EMU10K_PLAY:
+ portc->syncdir = DDI_DMA_SYNC_FORDEV;
+ caps = ENGINE_OUTPUT_CAP;
+ dir = DDI_DMA_WRITE;
+ portc->channels = 8;
+ portc->start_port = emu10k_start_play;
+ portc->stop_port = emu10k_stop_play;
+ portc->reset_port = emu10k_reset_play;
+ portc->update_port = emu10k_update_play;
+ /* XXX: this could probably be tunable */
+ portc->nfrags = 2;
+ portc->fragfr = 288;
+ portc->nframes = portc->nfrags * portc->fragfr;
+ portc->buf_size = portc->nframes * portc->channels * 2;
+ break;
+ default:
+ return (DDI_FAILURE);
+ }
+
+ /*
+ * Fragments that are not powers of two don't seem to work
+ * at all with EMU10K. For simplicity's sake, we eliminate
+ * the question and fix the interrupt rate. This is also the
+ * logical minimum for record, which requires at least 4K for
+ * the record size.
+ */
+
+ if (portc->buf_size > DMABUF_SIZE) {
+ cmn_err(CE_NOTE, "Buffer size %d is too large (max %d)",
+ (int)portc->buf_size, DMABUF_SIZE);
+ portc->buf_size = DMABUF_SIZE;
+ }
+
+ /* 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, portc->buf_size,
+ &dev_attr, DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL,
+ &portc->buf_kaddr, &len, &portc->buf_acch) != DDI_SUCCESS) {
+ audio_dev_warn(adev, "failed to allocate BUF memory");
+ return (DDI_FAILURE);
+ }
+
+ if (ddi_dma_addr_bind_handle(portc->buf_dmah, NULL, portc->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);
+ }
+ portc->buf_paddr = cookie.dmac_address;
+
+ if ((devc->feature_mask & SB_LIVE) &&
+ (portc->buf_paddr & 0x80000000)) {
+ audio_dev_warn(adev, "Got DMA buffer beyond 2G limit.");
+ return (DDI_FAILURE);
+ }
+
+ if (num == EMU10K_PLAY) { /* Output device */
+ n = portc->memptr / 4096;
+ /*
+ * Fill the page table
+ */
+ for (i = 0; i < portc->buf_size / 4096; i++) {
+ FILL_PAGE_MAP_ENTRY(n + i,
+ portc->buf_paddr + i * 4096);
+ }
+
+ (void) ddi_dma_sync(devc->pt_dmah, 0, 0, DDI_DMA_SYNC_FORDEV);
+ }
+
+ portc->engine = audio_engine_alloc(&emu10k_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);
+}
+
+int
+emu10k_setup_intrs(emu10k_devc_t *devc)
+{
+ uint_t ipri;
+ int actual;
+ int rv;
+ ddi_intr_handle_t ih[1];
+
+ rv = ddi_intr_alloc(devc->dip, ih, DDI_INTR_TYPE_FIXED,
+ 0, 1, &actual, DDI_INTR_ALLOC_STRICT);
+ if ((rv != DDI_SUCCESS) || (actual != 1)) {
+ audio_dev_warn(devc->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(devc->adev, "Can't get interrupt priority");
+ (void) ddi_intr_free(ih[0]);
+ return (DDI_FAILURE);
+ }
+
+ if (ddi_intr_add_handler(ih[0], emu10k_intr, devc, NULL) !=
+ DDI_SUCCESS) {
+ audio_dev_warn(devc->adev, "Can't add interrupt handler");
+ (void) ddi_intr_free(ih[0]);
+ return (DDI_FAILURE);
+ }
+
+ devc->ih = ih[0];
+ mutex_init(&devc->mutex, NULL, MUTEX_DRIVER, DDI_INTR_PRI(ipri));
+ return (DDI_SUCCESS);
+}
+
+void
+emu10k_destroy(emu10k_devc_t *devc)
+{
+ if (devc->ih != NULL) {
+ (void) ddi_intr_disable(devc->ih);
+ (void) ddi_intr_remove_handler(devc->ih);
+ (void) ddi_intr_free(devc->ih);
+ mutex_destroy(&devc->mutex);
+ }
+
+ if (devc->ksp) {
+ kstat_delete(devc->ksp);
+ }
+
+ if (devc->silence_paddr) {
+ (void) ddi_dma_unbind_handle(devc->silence_dmah);
+ }
+ if (devc->silence_acch) {
+ ddi_dma_mem_free(&devc->silence_acch);
+ }
+ if (devc->silence_dmah) {
+ ddi_dma_free_handle(&devc->silence_dmah);
+ }
+
+ if (devc->pt_paddr) {
+ (void) ddi_dma_unbind_handle(devc->pt_dmah);
+ }
+ if (devc->pt_acch) {
+ ddi_dma_mem_free(&devc->pt_acch);
+ }
+ if (devc->pt_dmah) {
+ ddi_dma_free_handle(&devc->pt_dmah);
+ }
+
+
+ for (int i = 0; i < CTL_MAX; i++) {
+ emu10k_ctrl_t *ec = &devc->ctrls[i];
+ if (ec->ctrl != NULL) {
+ audio_dev_del_control(ec->ctrl);
+ ec->ctrl = NULL;
+ }
+ }
+
+ for (int i = 0; i < EMU10K_NUM_PORTC; i++) {
+ emu10k_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->buf_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 != NULL) {
+ ac97_free(devc->ac97);
+ }
+ 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));
+}
+
+static void
+emu10k_init_voice(emu10k_devc_t *devc, int voice)
+{
+ emu10k_set_loop_stop(devc, voice, 1);
+
+ emu10k_write_reg(devc, VEDS, voice, 0x0);
+ emu10k_write_reg(devc, IP, voice, 0x0);
+ emu10k_write_reg(devc, VTFT, voice, 0xffff);
+ emu10k_write_reg(devc, CVCF, voice, 0xffff);
+ emu10k_write_reg(devc, PTAB, voice, 0x0);
+ emu10k_write_reg(devc, CPF, voice, 0x0);
+ emu10k_write_reg(devc, CCR, voice, 0x0);
+ emu10k_write_reg(devc, SCSA, voice, 0x0);
+ emu10k_write_reg(devc, SDL, voice, 0x10);
+ emu10k_write_reg(devc, QKBCA, voice, 0x0);
+ emu10k_write_reg(devc, Z1, voice, 0x0);
+ emu10k_write_reg(devc, Z2, voice, 0x0);
+
+ if (devc->feature_mask & (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL))
+ emu10k_write_reg(devc, SRDA, voice, 0x03020100);
+ else
+ emu10k_write_reg(devc, FXRT, voice, 0x32100000);
+
+ emu10k_write_reg(devc, MEHA, voice, 0x0);
+ emu10k_write_reg(devc, MEDS, voice, 0x0);
+ emu10k_write_reg(devc, IFA, voice, 0xffff);
+ emu10k_write_reg(devc, PEFE, voice, 0x0);
+ emu10k_write_reg(devc, VFM, voice, 0x0);
+ emu10k_write_reg(devc, TMFQ, voice, 24);
+ emu10k_write_reg(devc, VVFQ, voice, 24);
+ emu10k_write_reg(devc, TMPE, voice, 0x0);
+ emu10k_write_reg(devc, VLV, voice, 0x0);
+ emu10k_write_reg(devc, MLV, voice, 0x0);
+ emu10k_write_reg(devc, VEHA, voice, 0x0);
+ emu10k_write_reg(devc, VEV, voice, 0x0);
+ emu10k_write_reg(devc, MEV, voice, 0x0);
+
+ if (devc->feature_mask & (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL)) {
+ emu10k_write_reg(devc, CSBA, voice, 0x0);
+ emu10k_write_reg(devc, CSDC, voice, 0x0);
+ emu10k_write_reg(devc, CSFE, voice, 0x0);
+ emu10k_write_reg(devc, CSHG, voice, 0x0);
+ emu10k_write_reg(devc, SRHE, voice, 0x3f3f3f3f);
+ }
+}
+
+static void
+emu10k_refresh_mixer(emu10k_devc_t *devc)
+{
+ uint32_t val;
+ uint32_t set;
+
+ for (int gpr = 0; gpr < MAX_GPR; gpr++) {
+ if (devc->gpr_shadow[gpr].valid) {
+ emu10k_write_reg(devc, gpr + GPR0, 0,
+ devc->gpr_shadow[gpr].value);
+ }
+ }
+
+ set = devc->ctrls[CTL_JACK3].val;
+ if (devc->feature_mask & SB_INVSP) {
+ set = !set;
+ }
+
+ if (devc->feature_mask & (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL)) {
+ val = INL(devc, devc->regs + 0x18);
+ val &= ~A_IOCFG_GPOUT0;
+ val |= set ? 0x44 : 0x40;
+ OUTL(devc, val, devc->regs + 0x18);
+
+ } else if (devc->feature_mask & SB_LIVE) {
+ val = INL(devc, devc->regs + HCFG);
+ val &= ~HCFG_GPOUT0;
+ val |= set ? HCFG_GPOUT0 : 0;
+ OUTL(devc, val, devc->regs + HCFG);
+ }
+}
+
+int
+emu10k_hwinit(emu10k_devc_t *devc)
+{
+
+ unsigned int tmp, i;
+ unsigned int reg;
+
+ ASSERT(mutex_owned(&devc->mutex));
+
+ emu10k_write_reg(devc, AC97SLOT, 0, AC97SLOT_CENTER | AC97SLOT_LFE);
+
+ OUTL(devc, 0x00000000, devc->regs + 0x0c); /* Intr disable */
+ OUTL(devc, HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK |
+ HCFG_MUTEBUTTONENABLE,
+ devc->regs + HCFG);
+
+ emu10k_write_reg(devc, MBS, 0, 0x0);
+ emu10k_write_reg(devc, MBA, 0, 0x0);
+ emu10k_write_reg(devc, FXBS, 0, 0x0);
+ emu10k_write_reg(devc, FXBA, 0, 0x0);
+ emu10k_write_reg(devc, ADCBS, 0, 0x0);
+ emu10k_write_reg(devc, ADCBA, 0, 0x0);
+
+ OUTL(devc, 0, devc->regs + IE);
+ emu10k_write_reg(devc, CLIEL, 0, 0x0);
+ emu10k_write_reg(devc, CLIEH, 0, 0x0);
+ if (!(devc->feature_mask & SB_LIVE)) {
+ emu10k_write_reg(devc, HLIEL, 0, 0x0);
+ emu10k_write_reg(devc, HLIEH, 0, 0x0);
+ }
+ emu10k_write_reg(devc, CLIPL, 0, 0xffffffff);
+ emu10k_write_reg(devc, CLIPH, 0, 0xffffffff);
+ emu10k_write_reg(devc, SOLL, 0, 0xffffffff);
+ emu10k_write_reg(devc, SOLH, 0, 0xffffffff);
+
+
+ if (devc->feature_mask & (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL)) {
+ emu10k_write_reg(devc, SOC, 0, 0xf00); /* ?? */
+ emu10k_write_reg(devc, AC97SLOT, 0, 0x3); /* ?? */
+ }
+
+ for (i = 0; i < 64; i++)
+ emu10k_init_voice(devc, i);
+
+ emu10k_write_reg(devc, SCS0, 0, 0x2109204);
+ emu10k_write_reg(devc, SCS1, 0, 0x2109204);
+ emu10k_write_reg(devc, SCS2, 0, 0x2109204);
+
+ emu10k_write_reg(devc, PTBA, 0, devc->pt_paddr);
+ tmp = emu10k_read_reg(devc, PTBA, 0);
+
+ emu10k_write_reg(devc, TCBA, 0, 0x0);
+ emu10k_write_reg(devc, TCBS, 0, 0x4);
+
+ reg = 0;
+ if (devc->feature_mask & SB_71) {
+ reg = AC97SLOT_CENTER | AC97SLOT_LFE | AC97SLOT_REAR_LEFT |
+ AC97SLOT_REAR_RIGHT;
+ } else if (devc->feature_mask & SB_51) {
+ reg = AC97SLOT_CENTER | AC97SLOT_LFE;
+ }
+ if (devc->feature_mask & (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL))
+ reg |= 0x40;
+ emu10k_write_reg(devc, AC97SLOT, 0, reg);
+
+ if (devc->feature_mask & SB_AUDIGY2) {
+ /* Enable analog outputs on Audigy2 */
+ int tmp;
+
+ /* Setup SRCMulti_I2S SamplingRate */
+ tmp = emu10k_read_reg(devc, EHC, 0);
+ tmp &= 0xfffff1ff;
+ tmp |= (0x2 << 9);
+ emu10k_write_reg(devc, EHC, 0, tmp);
+ /* emu10k_write_reg (devc, SOC, 0, 0x00000000); */
+
+ /* Setup SRCSel (Enable Spdif,I2S SRCMulti) */
+ OUTL(devc, 0x600000, devc->regs + 0x20);
+ OUTL(devc, 0x14, devc->regs + 0x24);
+
+ /* Setup SRCMulti Input Audio Enable */
+ OUTL(devc, 0x6E0000, devc->regs + 0x20);
+
+ OUTL(devc, 0xFF00FF00, devc->regs + 0x24);
+
+ /* Setup I2S ASRC Enable (HC register) */
+ tmp = INL(devc, devc->regs + HCFG);
+ tmp |= 0x00000070;
+ OUTL(devc, tmp, devc->regs + HCFG);
+
+ /*
+ * Unmute Analog now. Set GPO6 to 1 for Apollo.
+ * This has to be done after init ALice3 I2SOut beyond 48KHz.
+ * So, sequence is important
+ */
+ tmp = INL(devc, devc->regs + 0x18);
+ tmp |= 0x0040;
+
+ OUTL(devc, tmp, devc->regs + 0x18);
+ }
+
+ if (devc->feature_mask & SB_AUDIGY2VAL) {
+ /* Enable analog outputs on Audigy2 */
+ int tmp;
+
+ /* Setup SRCMulti_I2S SamplingRate */
+ tmp = emu10k_read_reg(devc, EHC, 0);
+ tmp &= 0xfffff1ff;
+ tmp |= (0x2 << 9);
+ emu10k_write_reg(devc, EHC, 0, tmp);
+
+ /* Setup SRCSel (Enable Spdif,I2S SRCMulti) */
+ OUTL(devc, 0x600000, devc->regs + 0x20);
+ OUTL(devc, 0x14, devc->regs + 0x24);
+
+ /* Setup SRCMulti Input Audio Enable */
+ OUTL(devc, 0x7B0000, devc->regs + 0x20);
+ OUTL(devc, 0xFF000000, devc->regs + 0x24);
+
+ /* SPDIF output enable */
+ OUTL(devc, 0x7A0000, devc->regs + 0x20);
+ OUTL(devc, 0xFF000000, devc->regs + 0x24);
+
+ tmp = INL(devc, devc->regs + 0x18) & ~0x8;
+ OUTL(devc, tmp, devc->regs + 0x18);
+ }
+
+ emu10k_write_reg(devc, SOLL, 0, 0xffffffff);
+ emu10k_write_reg(devc, SOLH, 0, 0xffffffff);
+
+ if (devc->feature_mask & (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL)) {
+ unsigned int mode = 0;
+
+ if (devc->feature_mask & (SB_AUDIGY2|SB_AUDIGY2VAL))
+ mode |= HCFG_AC3ENABLE_GPSPDIF | HCFG_AC3ENABLE_CDSPDIF;
+ OUTL(devc,
+ HCFG_AUDIOENABLE | HCFG_AUTOMUTE |
+ HCFG_JOYENABLE | A_HCFG_VMUTE |
+ A_HCFG_AUTOMUTE | mode, devc->regs + HCFG);
+
+ OUTL(devc, INL(devc, devc->regs + 0x18) |
+ 0x0004, devc->regs + 0x18); /* GPIO (S/PDIF enable) */
+
+
+ /* enable IR port */
+ tmp = INL(devc, devc->regs + 0x18);
+ OUTL(devc, tmp | A_IOCFG_GPOUT2, devc->regs + 0x18);
+ drv_usecwait(500);
+ OUTL(devc, tmp | A_IOCFG_GPOUT1 | A_IOCFG_GPOUT2,
+ devc->regs + 0x18);
+ drv_usecwait(100);
+ OUTL(devc, tmp, devc->regs + 0x18);
+ } else {
+ OUTL(devc,
+ HCFG_AUDIOENABLE | HCFG_LOCKTANKCACHE_MASK |
+ HCFG_AUTOMUTE | HCFG_JOYENABLE, devc->regs + HCFG);
+ }
+
+
+ /* enable IR port */
+ tmp = INL(devc, devc->regs + HCFG);
+ OUTL(devc, tmp | HCFG_GPOUT2, devc->regs + HCFG);
+ drv_usecwait(500);
+ OUTL(devc, tmp | HCFG_GPOUT1 | HCFG_GPOUT2, devc->regs + HCFG);
+ drv_usecwait(100);
+ OUTL(devc, tmp, devc->regs + HCFG);
+
+
+ /*
+ * Start by configuring for analog mode.
+ */
+ if (devc->feature_mask & (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL)) {
+ reg = INL(devc, devc->regs + 0x18) & ~A_IOCFG_GPOUT0;
+ reg |= ((devc->feature_mask & SB_INVSP) ? 0x4 : 0);
+ OUTL(devc, reg, devc->regs + 0x18);
+ }
+ if (devc->feature_mask & SB_LIVE) { /* SBLIVE */
+ reg = INL(devc, devc->regs + HCFG) & ~HCFG_GPOUT0;
+ reg |= ((devc->feature_mask & SB_INVSP) ? HCFG_GPOUT0 : 0);
+ OUTL(devc, reg, devc->regs + HCFG);
+ }
+
+ if (devc->feature_mask & SB_AUDIGY2VAL) {
+ OUTL(devc, INL(devc, devc->regs + 0x18) | 0x0060,
+ devc->regs + 0x18);
+ } else if (devc->feature_mask & SB_AUDIGY2) {
+ OUTL(devc, INL(devc, devc->regs + 0x18) | 0x0040,
+ devc->regs + 0x18);
+ } else if (devc->feature_mask & SB_AUDIGY) {
+ OUTL(devc, INL(devc, devc->regs + 0x18) | 0x0080,
+ devc->regs + 0x18);
+ }
+
+ emu10k_init_effects(devc);
+
+ return (DDI_SUCCESS);
+}
+
+static const int db2lin_101[101] = {
+ 0x00000000,
+ 0x0024B53A, 0x002750CA, 0x002A1BC6, 0x002D198D, 0x00304DBA, 0x0033BC2A,
+ 0x00376901, 0x003B58AF, 0x003F8FF1, 0x004413DF, 0x0048E9EA, 0x004E17E9,
+ 0x0053A419, 0x0059952C, 0x005FF24E, 0x0066C32A, 0x006E0FFB, 0x0075E18D,
+ 0x007E414F, 0x0087395B, 0x0090D482, 0x009B1E5B, 0x00A6234F, 0x00B1F0A7,
+ 0x00BE94A1, 0x00CC1E7C, 0x00DA9E8D, 0x00EA2650, 0x00FAC881, 0x010C9931,
+ 0x011FADDC, 0x01341D87, 0x014A00D8, 0x01617235, 0x017A8DE6, 0x01957233,
+ 0x01B23F8D, 0x01D118B1, 0x01F222D4, 0x021585D1, 0x023B6C57, 0x0264041D,
+ 0x028F7E19, 0x02BE0EBD, 0x02EFEE33, 0x032558A2, 0x035E8E7A, 0x039BD4BC,
+ 0x03DD7551, 0x0423BF61, 0x046F07B5, 0x04BFA91B, 0x051604D5, 0x0572830D,
+ 0x05D59354, 0x063FAD27, 0x06B15080, 0x072B0673, 0x07AD61CD, 0x0838FFCA,
+ 0x08CE88D3, 0x096EB147, 0x0A1A3A53, 0x0AD1F2E0, 0x0B96B889, 0x0C6978A5,
+ 0x0D4B316A, 0x0E3CF31B, 0x0F3FE155, 0x10553469, 0x117E3AD9, 0x12BC5AEA,
+ 0x14111457, 0x157E0219, 0x1704DC5E, 0x18A77A97, 0x1A67D5B6, 0x1C480A87,
+ 0x1E4A5C45, 0x2071374D, 0x22BF3412, 0x25371A37, 0x27DBE3EF, 0x2AB0C18F,
+ 0x2DB91D6F, 0x30F89FFD, 0x34733433, 0x382D0C46, 0x3C2AA6BD, 0x4070D3D9,
+ 0x4504BB66, 0x49EBE2F1, 0x4F2C346F, 0x54CC0565, 0x5AD21E86, 0x6145C3E7,
+ 0x682EBDBD, 0x6F9561C4, 0x77829D4D,
+ 0x7fffffff
+};
+
+static int
+emu10k_convert_fixpoint(int val)
+{
+ if (val < 0)
+ val = 0;
+ if (val > 100)
+ val = 100;
+ return (db2lin_101[val]);
+}
+
+static void
+emu10k_write_gpr(emu10k_devc_t *devc, int gpr, uint32_t value)
+{
+ ASSERT(gpr < MAX_GPR);
+ devc->gpr_shadow[gpr].valid = B_TRUE;
+ devc->gpr_shadow[gpr].value = value;
+ if (!devc->suspended) {
+ emu10k_write_reg(devc, gpr + GPR0, 0, value);
+ }
+}
+
+static int
+emu10k_set_stereo(void *arg, uint64_t val)
+{
+ emu10k_ctrl_t *ec = arg;
+ emu10k_devc_t *devc = ec->devc;
+ uint32_t left, right;
+
+ left = (val >> 8) & 0xff;
+ right = val & 0xff;
+ if ((left > 100) || (right > 100) || (val & ~(0xffff)))
+ return (EINVAL);
+
+ left = emu10k_convert_fixpoint(left);
+ right = emu10k_convert_fixpoint(right);
+
+ mutex_enter(&devc->mutex);
+ ec->val = val;
+
+ emu10k_write_gpr(devc, ec->gpr_num, left);
+ emu10k_write_gpr(devc, ec->gpr_num + 1, right);
+
+ mutex_exit(&devc->mutex);
+ return (0);
+}
+
+static int
+emu10k_set_mono(void *arg, uint64_t val)
+{
+ emu10k_ctrl_t *ec = arg;
+ emu10k_devc_t *devc = ec->devc;
+ uint32_t v;
+
+ if (val > 100)
+ return (EINVAL);
+
+ v = emu10k_convert_fixpoint(val & 0xff);
+
+ mutex_enter(&devc->mutex);
+ ec->val = val;
+ emu10k_write_gpr(devc, ec->gpr_num, v);
+ mutex_exit(&devc->mutex);
+ return (0);
+}
+
+static int
+emu10k_get_control(void *arg, uint64_t *val)
+{
+ emu10k_ctrl_t *ec = arg;
+ emu10k_devc_t *devc = ec->devc;
+
+ mutex_enter(&devc->mutex);
+ *val = ec->val;
+ mutex_exit(&devc->mutex);
+ return (0);
+}
+
+#define PLAYCTL (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_PLAY)
+#define RECCTL (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_REC)
+#define MONCTL (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_MONITOR)
+#define MAINVOL (PLAYCTL | AUDIO_CTRL_FLAG_MAINVOL)
+#define PCMVOL (PLAYCTL | AUDIO_CTRL_FLAG_PCMVOL)
+#define RECVOL (RECCTL | AUDIO_CTRL_FLAG_RECVOL)
+#define MONVOL (MONCTL | AUDIO_CTRL_FLAG_MONVOL)
+
+static int
+emu10k_get_ac97src(void *arg, uint64_t *valp)
+{
+ ac97_ctrl_t *ctrl = arg;
+
+ return (ac97_control_get(ctrl, valp));
+}
+
+static int
+emu10k_set_ac97src(void *arg, uint64_t value)
+{
+ ac97_ctrl_t *ctrl = arg;
+
+ return (ac97_control_set(ctrl, value));
+}
+
+static int
+emu10k_set_jack3(void *arg, uint64_t value)
+{
+ emu10k_ctrl_t *ec = arg;
+ emu10k_devc_t *devc = ec->devc;
+ uint32_t set_val;
+ uint32_t val;
+
+ set_val = ddi_ffs(value & 0xffffffffU);
+ set_val--;
+ mutex_enter(&devc->mutex);
+ switch (set_val) {
+ case 0:
+ case 1:
+ break;
+ default:
+ mutex_exit(&devc->mutex);
+ return (EINVAL);
+ }
+ ec->val = value;
+ /* center/lfe */
+ if (devc->feature_mask & SB_INVSP) {
+ set_val = !set_val;
+ }
+ if (!devc->suspended) {
+ if (devc->feature_mask & (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL)) {
+ val = INL(devc, devc->regs + 0x18);
+ val &= ~A_IOCFG_GPOUT0;
+ val |= set_val ? 0x44 : 0x40;
+ OUTL(devc, val, devc->regs + 0x18);
+
+ } else if (devc->feature_mask & SB_LIVE) {
+ val = INL(devc, devc->regs + HCFG);
+ val &= ~HCFG_GPOUT0;
+ val |= set_val ? HCFG_GPOUT0 : 0;
+ OUTL(devc, val, devc->regs + HCFG);
+ }
+ }
+ mutex_exit(&devc->mutex);
+ return (0);
+}
+
+static int
+emu10k_set_recsrc(void *arg, uint64_t value)
+{
+ emu10k_ctrl_t *ec = arg;
+ emu10k_devc_t *devc = ec->devc;
+ uint32_t set_val;
+
+ set_val = ddi_ffs(value & 0xffffffffU);
+ set_val--;
+
+ /*
+ * We start assuming well set up AC'97 for stereomix recording.
+ */
+ switch (set_val) {
+ case INPUT_AC97:
+ case INPUT_SPD1:
+ case INPUT_SPD2:
+ case INPUT_DIGCD:
+ case INPUT_AUX2:
+ case INPUT_LINE2:
+ case INPUT_STEREOMIX:
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ mutex_enter(&devc->mutex);
+ ec->val = value;
+
+ emu10k_write_gpr(devc, GPR_REC_AC97, (set_val == INPUT_AC97));
+ emu10k_write_gpr(devc, GPR_REC_SPDIF1, (set_val == INPUT_SPD1));
+ emu10k_write_gpr(devc, GPR_REC_SPDIF2, (set_val == INPUT_SPD2));
+ emu10k_write_gpr(devc, GPR_REC_DIGCD, (set_val == INPUT_DIGCD));
+ emu10k_write_gpr(devc, GPR_REC_AUX2, (set_val == INPUT_AUX2));
+ emu10k_write_gpr(devc, GPR_REC_LINE2, (set_val == INPUT_LINE2));
+ emu10k_write_gpr(devc, GPR_REC_PCM, (set_val == INPUT_STEREOMIX));
+
+ mutex_exit(&devc->mutex);
+
+ return (0);
+}
+
+static void
+emu10k_create_stereo(emu10k_devc_t *devc, int ctl, int gpr,
+ const char *id, int flags, int defval)
+{
+ emu10k_ctrl_t *ec;
+ audio_ctrl_desc_t desc;
+
+ bzero(&desc, sizeof (desc));
+
+ ec = &devc->ctrls[ctl];
+ ec->devc = devc;
+ ec->gpr_num = gpr;
+
+ desc.acd_name = id;
+ desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
+ desc.acd_minvalue = 0;
+ desc.acd_maxvalue = 100;
+ desc.acd_flags = flags;
+
+ ec->val = (defval << 8) | defval;
+ ec->ctrl = audio_dev_add_control(devc->adev, &desc,
+ emu10k_get_control, emu10k_set_stereo, ec);
+
+ mutex_enter(&devc->mutex);
+ emu10k_write_gpr(devc, gpr, emu10k_convert_fixpoint(defval));
+ emu10k_write_gpr(devc, gpr + 1, emu10k_convert_fixpoint(defval));
+ mutex_exit(&devc->mutex);
+}
+
+static void
+emu10k_create_mono(emu10k_devc_t *devc, int ctl, int gpr,
+ const char *id, int flags, int defval)
+{
+ emu10k_ctrl_t *ec;
+ audio_ctrl_desc_t desc;
+
+ bzero(&desc, sizeof (desc));
+
+ ec = &devc->ctrls[ctl];
+ ec->devc = devc;
+ ec->gpr_num = gpr;
+
+ desc.acd_name = id;
+ desc.acd_type = AUDIO_CTRL_TYPE_MONO;
+ desc.acd_minvalue = 0;
+ desc.acd_maxvalue = 100;
+ desc.acd_flags = flags;
+
+ ec->val = defval;
+ ec->ctrl = audio_dev_add_control(devc->adev, &desc,
+ emu10k_get_control, emu10k_set_mono, ec);
+
+ mutex_enter(&devc->mutex);
+ emu10k_write_gpr(devc, gpr, emu10k_convert_fixpoint(defval));
+ mutex_exit(&devc->mutex);
+}
+
+/*
+ * AC'97 source. The AC'97 PCM record channel is routed to our
+ * mixer. While we could support the direct monitoring capability of
+ * the AC'97 part itself, this would not work correctly with outputs
+ * that are not routed via AC'97 (such as the Live Drive headphones
+ * or digital outputs.) So we just offer the ability to select one
+ * AC'97 source, and then offer independent ability to either monitor
+ * or record from the AC'97 mixer's PCM record channel.
+ */
+static void
+emu10k_create_ac97src(emu10k_devc_t *devc)
+{
+ emu10k_ctrl_t *ec;
+ audio_ctrl_desc_t desc;
+ ac97_ctrl_t *ac;
+ const audio_ctrl_desc_t *acd;
+
+ bzero(&desc, sizeof (desc));
+
+ ec = &devc->ctrls[CTL_AC97SRC];
+ desc.acd_name = "ac97-source";
+ desc.acd_type = AUDIO_CTRL_TYPE_ENUM;
+ desc.acd_flags = RECCTL;
+ ec->devc = devc;
+ ac = ac97_control_find(devc->ac97, AUDIO_CTRL_ID_RECSRC);
+ if (ac == NULL) {
+ return;
+ }
+
+ acd = ac97_control_desc(ac);
+
+ for (int i = 0; i < 64; i++) {
+ const char *n;
+ if (((acd->acd_minvalue & (1ULL << i)) == 0) ||
+ ((n = acd->acd_enum[i]) == NULL)) {
+ continue;
+ }
+ desc.acd_enum[i] = acd->acd_enum[i];
+ /* we suppress some port options */
+ if ((strcmp(n, AUDIO_PORT_STEREOMIX) == 0) ||
+ (strcmp(n, AUDIO_PORT_MONOMIX) == 0) ||
+ (strcmp(n, AUDIO_PORT_VIDEO) == 0)) {
+ continue;
+ }
+ desc.acd_minvalue |= (1ULL << i);
+ desc.acd_maxvalue |= (1ULL << i);
+ }
+
+ ec->ctrl = audio_dev_add_control(devc->adev, &desc,
+ emu10k_get_ac97src, emu10k_set_ac97src, ac);
+}
+
+/*
+ * Record source... this one is tricky. While the chip will
+ * conceivably let us *mix* some of the audio streams for recording,
+ * the AC'97 inputs don't have this capability. Offering it to users
+ * is likely to be confusing, so we offer a single record source
+ * selection option. Its not ideal, but it ought to be good enough
+ * for the vast majority of users.
+ */
+static void
+emu10k_create_recsrc(emu10k_devc_t *devc)
+{
+ emu10k_ctrl_t *ec;
+ audio_ctrl_desc_t desc;
+ ac97_ctrl_t *ac;
+
+ bzero(&desc, sizeof (desc));
+
+ ec = &devc->ctrls[CTL_RECSRC];
+ desc.acd_name = AUDIO_CTRL_ID_RECSRC;
+ desc.acd_type = AUDIO_CTRL_TYPE_ENUM;
+ desc.acd_flags = RECCTL;
+ desc.acd_minvalue = 0;
+ desc.acd_maxvalue = 0;
+ bzero(desc.acd_enum, sizeof (desc.acd_enum));
+ ec->devc = devc;
+ ac = ac97_control_find(devc->ac97, AUDIO_CTRL_ID_RECSRC);
+
+ /* only low order bits set by AC'97 */
+ ASSERT(desc.acd_minvalue == desc.acd_maxvalue);
+ ASSERT((desc.acd_minvalue & ~0xffff) == 0);
+
+ /*
+ * It would be really cool if we could detect whether these
+ * options are all sensible on a given configuration. Units
+ * without live-drive support, and units without a physical
+ * live-drive, simply can't do all these.
+ */
+ if (ac != NULL) {
+ desc.acd_minvalue |= (1 << INPUT_AC97);
+ desc.acd_maxvalue |= (1 << INPUT_AC97);
+ desc.acd_enum[INPUT_AC97] = "ac97";
+ ec->val = (1 << INPUT_AC97);
+ } else {
+ /* next best guess */
+ ec->val = (1 << INPUT_LINE2);
+ }
+
+ desc.acd_minvalue |= (1 << INPUT_SPD1);
+ desc.acd_maxvalue |= (1 << INPUT_SPD1);
+ desc.acd_enum[INPUT_SPD1] = AUDIO_PORT_SPDIFIN;
+
+ desc.acd_minvalue |= (1 << INPUT_SPD2);
+ desc.acd_maxvalue |= (1 << INPUT_SPD2);
+ desc.acd_enum[INPUT_SPD2] = "spdif2-in";
+
+ desc.acd_minvalue |= (1 << INPUT_DIGCD);
+ desc.acd_maxvalue |= (1 << INPUT_DIGCD);
+ desc.acd_enum[INPUT_DIGCD] = "digital-cd";
+
+ desc.acd_minvalue |= (1 << INPUT_AUX2);
+ desc.acd_maxvalue |= (1 << INPUT_AUX2);
+ desc.acd_enum[INPUT_AUX2] = AUDIO_PORT_AUX2IN;
+
+ desc.acd_minvalue |= (1 << INPUT_LINE2);
+ desc.acd_maxvalue |= (1 << INPUT_LINE2);
+ desc.acd_enum[INPUT_LINE2] = "line2-in";
+
+ desc.acd_minvalue |= (1 << INPUT_STEREOMIX);
+ desc.acd_maxvalue |= (1 << INPUT_STEREOMIX);
+ desc.acd_enum[INPUT_STEREOMIX] = AUDIO_PORT_STEREOMIX;
+
+ emu10k_write_gpr(devc, GPR_REC_SPDIF1, 0);
+ emu10k_write_gpr(devc, GPR_REC_SPDIF2, 0);
+ emu10k_write_gpr(devc, GPR_REC_DIGCD, 0);
+ emu10k_write_gpr(devc, GPR_REC_AUX2, 0);
+ emu10k_write_gpr(devc, GPR_REC_LINE2, 0);
+ emu10k_write_gpr(devc, GPR_REC_PCM, 0);
+ emu10k_write_gpr(devc, GPR_REC_AC97, 1);
+
+ ec->ctrl = audio_dev_add_control(devc->adev, &desc,
+ emu10k_get_control, emu10k_set_recsrc, ec);
+}
+
+static void
+emu10k_create_jack3(emu10k_devc_t *devc)
+{
+ emu10k_ctrl_t *ec;
+ audio_ctrl_desc_t desc;
+
+ bzero(&desc, sizeof (desc));
+
+ ec = &devc->ctrls[CTL_JACK3];
+ desc.acd_name = AUDIO_CTRL_ID_JACK3;
+ desc.acd_type = AUDIO_CTRL_TYPE_ENUM;
+ desc.acd_flags = AUDIO_CTRL_FLAG_RW;
+ desc.acd_minvalue = 0x3;
+ desc.acd_maxvalue = 0x3;
+ bzero(desc.acd_enum, sizeof (desc.acd_enum));
+ ec->devc = devc;
+ ec->val = 0x1;
+
+ desc.acd_enum[0] = AUDIO_PORT_CENLFE;
+ desc.acd_enum[1] = AUDIO_PORT_SPDIFOUT;
+
+ ec->ctrl = audio_dev_add_control(devc->adev, &desc,
+ emu10k_get_control, emu10k_set_jack3, ec);
+}
+
+
+static void
+emu10k_create_controls(emu10k_devc_t *devc)
+{
+ ac97_t *ac97;
+ ac97_ctrl_t *ac;
+
+ emu10k_create_mono(devc, CTL_VOLUME, GPR_VOL_PCM,
+ AUDIO_CTRL_ID_VOLUME, PCMVOL, 75);
+
+ emu10k_create_stereo(devc, CTL_FRONT, GPR_VOL_FRONT,
+ AUDIO_CTRL_ID_FRONT, MAINVOL, 100);
+ emu10k_create_stereo(devc, CTL_SURROUND, GPR_VOL_SURR,
+ AUDIO_CTRL_ID_SURROUND, MAINVOL, 100);
+ if (devc->feature_mask & (SB_51 | SB_71)) {
+ emu10k_create_mono(devc, CTL_CENTER, GPR_VOL_CEN,
+ AUDIO_CTRL_ID_CENTER, MAINVOL, 100);
+ emu10k_create_mono(devc, CTL_LFE, GPR_VOL_LFE,
+ AUDIO_CTRL_ID_LFE, MAINVOL, 100);
+ }
+ if (devc->feature_mask & SB_71) {
+ emu10k_create_stereo(devc, CTL_SIDE, GPR_VOL_SIDE,
+ "side", MAINVOL, 100);
+ }
+
+ emu10k_create_stereo(devc, CTL_RECGAIN, GPR_VOL_REC,
+ AUDIO_CTRL_ID_RECGAIN, RECVOL, 50);
+
+ emu10k_create_ac97src(devc);
+ emu10k_create_recsrc(devc);
+ /*
+ * 5.1 devices have versa jack. Note that from what we can
+ * tell, none of the 7.1 devices have or need this versa jack,
+ * as they all seem to have a dedicated digital I/O port.
+ */
+ if ((devc->feature_mask & SB_51) &&
+ !(devc->feature_mask & SB_AUDIGY2VAL)) {
+ emu10k_create_jack3(devc);
+ }
+
+ /* these ones AC'97 can manage directly */
+ ac97 = devc->ac97;
+
+ if ((ac = ac97_control_find(ac97, AUDIO_CTRL_ID_MICBOOST)) != NULL)
+ ac97_control_register(ac);
+ if ((ac = ac97_control_find(ac97, AUDIO_CTRL_ID_MICGAIN)) != NULL)
+ ac97_control_register(ac);
+
+ /* set any AC'97 analog outputs to full volume (no attenuation) */
+ if ((ac = ac97_control_find(ac97, AUDIO_CTRL_ID_FRONT)) != NULL)
+ ac97_control_set(ac, (100 << 8) | 100);
+ if ((ac = ac97_control_find(ac97, AUDIO_CTRL_ID_LINEOUT)) != NULL)
+ ac97_control_set(ac, (100 << 8) | 100);
+ if ((ac = ac97_control_find(ac97, AUDIO_CTRL_ID_SURROUND)) != NULL)
+ ac97_control_set(ac, (100 << 8) | 100);
+ if ((ac = ac97_control_find(ac97, AUDIO_CTRL_ID_CENTER)) != NULL)
+ ac97_control_set(ac, 100);
+ if ((ac = ac97_control_find(ac97, AUDIO_CTRL_ID_LFE)) != NULL)
+ ac97_control_set(ac, 100);
+
+ /* Monitor sources */
+ emu10k_create_stereo(devc, CTL_AC97, GPR_MON_AC97,
+ "ac97-monitor", MONVOL, 0);
+ emu10k_create_stereo(devc, CTL_SPD1, GPR_MON_SPDIF1,
+ AUDIO_PORT_SPDIFIN, MONVOL, 0);
+ emu10k_create_stereo(devc, CTL_DIGCD, GPR_MON_DIGCD,
+ "digital-cd", MONVOL, 0);
+ emu10k_create_stereo(devc, CTL_SPD1, GPR_MON_SPDIF1,
+ AUDIO_PORT_SPDIFIN, MONVOL, 0);
+
+ if ((devc->feature_mask & SB_NOEXP) == 0) {
+ /*
+ * These ports are only available via an external
+ * expansion box. Don't expose them for cards that
+ * don't have support for it.
+ */
+ emu10k_create_stereo(devc, CTL_HEADPH, GPR_VOL_HEADPH,
+ AUDIO_CTRL_ID_HEADPHONE, MAINVOL, 100);
+ emu10k_create_stereo(devc, CTL_SPD2, GPR_MON_SPDIF2,
+ "spdif2-in", MONVOL, 0);
+ emu10k_create_stereo(devc, CTL_LINE2, GPR_MON_LINE2,
+ "line2-in", MONVOL, 0);
+ emu10k_create_stereo(devc, CTL_AUX2, GPR_MON_AUX2,
+ AUDIO_PORT_AUX2IN, MONVOL, 0);
+ }
+}
+
+static void
+emu10k_load_dsp(emu10k_devc_t *devc, uint32_t *code, int ncode,
+ uint32_t *init, int ninit)
+{
+ int i;
+
+ if (ncode > 1024) {
+ audio_dev_warn(devc->adev, "DSP file size too big");
+ return;
+ }
+ if (ninit > MAX_GPR) {
+ audio_dev_warn(devc->adev, "Too many inits");
+ return;
+ }
+
+ /* Upload our DSP code */
+ for (i = 0; i < ncode; i++) {
+ emu10k_write_efx(devc, UC0 + i, code[i]);
+ }
+
+ /* Upload the initialization settings */
+ for (i = 0; i < ninit; i += 2) {
+ emu10k_write_reg(devc, init[i] + GPR0, 0, init[i + 1]);
+ }
+}
+
+#define LIVE_NOP() \
+ emu10k_write_efx(devc, UC0 + (pc * 2), 0x10040); \
+ emu10k_write_efx(devc, UC0 + (pc * 2 + 1), 0x610040); \
+ pc++
+#define LIVE_ACC3(r, a, x, y) /* z=w+x+y */ \
+ emu10k_write_efx(devc, UC0 + (pc * 2), (x << 10) | y); \
+ emu10k_write_efx(devc, UC0 + (pc * 2 + 1), (6 << 20) | (r << 10) | a); \
+ pc++
+
+#define AUDIGY_ACC3(r, a, x, y) /* z=w+x+y */ \
+ emu10k_write_efx(devc, UC0 + (pc * 2), (x << 12) | y); \
+ emu10k_write_efx(devc, UC0 + (pc * 2+1), (6 << 24) | (r << 12) | a); \
+ pc++
+#define AUDIGY_NOP() AUDIGY_ACC3(0xc0, 0xc0, 0xc0, 0xc0)
+
+static void
+emu10k_init_effects(emu10k_devc_t *devc)
+{
+ int i;
+ unsigned short pc;
+
+ ASSERT(mutex_owned(&devc->mutex));
+
+ if (devc->feature_mask & (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL)) {
+ pc = 0;
+ for (i = 0; i < 512; i++) {
+ AUDIGY_NOP();
+ }
+
+ for (i = 0; i < 256; i++)
+ emu10k_write_efx(devc, GPR0 + i, 0);
+ emu10k_write_reg(devc, AUDIGY_DBG, 0, 0);
+ emu10k_load_dsp(devc,
+ emu10k2_code,
+ sizeof (emu10k2_code) / sizeof (emu10k2_code[0]),
+ emu10k2_init,
+ sizeof (emu10k2_init) / sizeof (emu10k2_init[0]));
+
+ } else {
+ pc = 0;
+ for (i = 0; i < 512; i++) {
+ LIVE_NOP();
+ }
+
+ for (i = 0; i < 256; i++)
+ emu10k_write_efx(devc, GPR0 + i, 0);
+ emu10k_write_reg(devc, DBG, 0, 0);
+ emu10k_load_dsp(devc,
+ emu10k1_code,
+ sizeof (emu10k1_code) / sizeof (emu10k1_code[0]),
+ emu10k1_init,
+ sizeof (emu10k1_init) / sizeof (emu10k1_init[0]));
+ }
+}
+
+/* mixer */
+
+static struct {
+ uint16_t devid;
+ uint16_t subid;
+ const char *model;
+ const char *prod;
+ unsigned feature_mask;
+} emu10k_cards[] = {
+ { 0x2, 0x0020, "CT4670", "Live! Value", SB_LIVE | SB_NOEXP },
+ { 0x2, 0x0021, "CT4621", "Live!", SB_LIVE },
+ { 0x2, 0x100a, "SB0220", "Live! 5.1 Digital",
+ SB_LIVE | SB_51 | SB_NOEXP },
+ { 0x2, 0x8022, "CT4780", "Live! Value", SB_LIVE },
+ { 0x2, 0x8023, "CT4790", "PCI512", SB_LIVE | SB_NOEXP },
+ { 0x2, 0x8026, "CT4830", "Live! Value", SB_LIVE },
+ { 0x2, 0x8028, "CT4870", "Live! Value", SB_LIVE },
+ { 0x2, 0x8031, "CT4831", "Live! Value", SB_LIVE },
+ { 0x2, 0x8040, "CT4760", "Live!", SB_LIVE },
+ { 0x2, 0x8051, "CT4850", "Live! Value", SB_LIVE },
+ { 0x2, 0x8061, "SB0060", "Live! 5.1", SB_LIVE | SB_51 },
+ { 0x2, 0x8064, "SB0100", "Live! 5.1", SB_LIVE | SB_51 },
+ { 0x2, 0x8065, "SB0220", "Live! 5.1", SB_LIVE | SB_51 },
+ { 0x2, 0x8066, "SB0228", "Live! 5.1", SB_LIVE | SB_51 },
+ { 0x4, 0x0051, "SB0090", "Audigy", SB_AUDIGY | SB_51 },
+ { 0x4, 0x0052, "SB0160", "Audigy ES", SB_AUDIGY | SB_51 },
+ { 0x4, 0x0053, "SB0092", "Audigy", SB_AUDIGY | SB_51 },
+ { 0x4, 0x1002, "SB0240P", "Audigy 2 Platinum",
+ SB_AUDIGY2 | SB_71 | SB_INVSP },
+ { 0x4, 0x1003, "SB0353", "Audigy 2 ZS", SB_AUDIGY2 | SB_71 | SB_INVSP },
+ { 0x4, 0x1005, "SB0280", "Audigy 2 Platinum EX", SB_AUDIGY2 | SB_71 },
+ { 0x4, 0x1007, "SB0240", "Audigy 2", SB_AUDIGY2 | SB_71 },
+ { 0x4, 0x2001, "SB0360", "Audigy 2 ZS", SB_AUDIGY2 | SB_71 | SB_INVSP },
+ { 0x4, 0x2002, "SB0350", "Audigy 2 ZS", SB_AUDIGY2 | SB_71 | SB_INVSP },
+ { 0x4, 0x2006, "SB0350", "Audigy 2", SB_AUDIGY2 | SB_71 | SB_INVSP },
+ { 0x4, 0x2007, "SB0380", "Audigy 4 Pro", SB_AUDIGY2 | SB_71 },
+ { 0x8, 0x1001, "SB0400", "Audigy 2 Value",
+ SB_AUDIGY2VAL | SB_71 | SB_NOEXP },
+ { 0x8, 0x1021, "SB0610", "Audigy 4",
+ SB_AUDIGY2VAL | SB_71 | SB_NOEXP },
+ { 0x8, 0x2001, "SB0530", "Audigy 2 ZS Notebook",
+ SB_AUDIGY2VAL | SB_71 },
+ { 0, 0, NULL, NULL, 0 },
+};
+
+int
+emu10k_attach(dev_info_t *dip)
+{
+ uint16_t pci_command;
+ uint16_t subid;
+ uint16_t devid;
+ emu10k_devc_t *devc;
+ ddi_acc_handle_t pcih;
+ ddi_dma_cookie_t cookie;
+ uint_t count;
+ ulong_t len;
+ int i;
+ const char *name;
+ const char *model;
+ char namebuf[64];
+ int feature_mask;
+
+ devc = kmem_zalloc(sizeof (*devc), KM_SLEEP);
+ devc->dip = dip;
+ ddi_set_driver_private(dip, devc);
+
+ 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;
+
+ devid = pci_config_get16(pcih, PCI_CONF_DEVID);
+ subid = pci_config_get16(pcih, PCI_CONF_SUBSYSID);
+
+ 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->regs, 0, 0, &dev_attr,
+ &devc->regsh)) != DDI_SUCCESS) {
+ audio_dev_warn(devc->adev, "failed to map registers");
+ goto error;
+ }
+
+ switch (devid) {
+ case PCI_DEVICE_ID_SBLIVE:
+ name = "Live!";
+ model = "CT????";
+ feature_mask = SB_LIVE;
+ break;
+
+ case PCI_DEVICE_ID_AUDIGYVALUE:
+ name = "Audigy 2 Value";
+ model = "SB????";
+ feature_mask = SB_AUDIGY2VAL;
+ break;
+
+ case PCI_DEVICE_ID_AUDIGY:
+ if (subid >= 0x1002 && subid <= 0x2005) {
+ name = "Audigy 2";
+ model = "SB????";
+ feature_mask = SB_AUDIGY2;
+ } else {
+ name = "Audigy";
+ model = "SB????";
+ feature_mask = SB_AUDIGY;
+ }
+ break;
+
+ default:
+ audio_dev_warn(devc->adev, "Unrecognized device");
+ goto error;
+ }
+
+ for (i = 0; emu10k_cards[i].prod; i++) {
+ if ((devid == emu10k_cards[i].devid) &&
+ (subid == emu10k_cards[i].subid)) {
+ name = emu10k_cards[i].prod;
+ model = emu10k_cards[i].model;
+ feature_mask = emu10k_cards[i].feature_mask;
+ break;
+ }
+ }
+ devc->feature_mask = feature_mask;
+
+ (void) snprintf(namebuf, sizeof (namebuf), "Sound Blaster %s", name);
+
+ audio_dev_set_description(devc->adev, namebuf);
+ audio_dev_set_version(devc->adev, model);
+
+ if (emu10k_setup_intrs(devc) != DDI_SUCCESS)
+ goto error;
+
+ /* allocate static page table memory */
+
+ devc->max_mem = AUDIO_MEMSIZE;
+
+ /* SB Live/Audigy supports at most 32M of memory) */
+ if (devc->max_mem > 32 * 1024 * 1024)
+ devc->max_mem = 32 * 1024 * 1024;
+
+ devc->max_pages = devc->max_mem / 4096;
+ if (devc->max_pages < 1024)
+ devc->max_pages = 1024;
+
+ /* Allocate page table */
+ if (ddi_dma_alloc_handle(devc->dip, &dma_attr_buf, DDI_DMA_SLEEP, NULL,
+ &devc->pt_dmah) != DDI_SUCCESS) {
+ audio_dev_warn(devc->adev,
+ "failed to allocate page table handle");
+ return (DDI_FAILURE);
+ }
+
+ if (ddi_dma_mem_alloc(devc->pt_dmah, devc->max_pages * 4,
+ &dev_attr, DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL,
+ &devc->pt_kaddr, &len, &devc->pt_acch) !=
+ DDI_SUCCESS) {
+ audio_dev_warn(devc->adev,
+ "failed to allocate memory for page table");
+ return (DDI_FAILURE);
+ }
+
+ if (ddi_dma_addr_bind_handle(devc->pt_dmah, NULL,
+ devc->pt_kaddr, len, DDI_DMA_CONSISTENT | DDI_DMA_WRITE,
+ DDI_DMA_SLEEP, NULL, &cookie, &count) != DDI_SUCCESS) {
+ audio_dev_warn(devc->adev,
+ "failed binding page table DMA handle");
+ return (DDI_FAILURE);
+ }
+
+ devc->page_map = (void *)devc->pt_kaddr;
+ devc->pt_paddr = cookie.dmac_address;
+ bzero(devc->pt_kaddr, devc->max_pages * 4);
+
+ /* Allocate silent page */
+ if (ddi_dma_alloc_handle(devc->dip, &dma_attr_buf, DDI_DMA_SLEEP, NULL,
+ &devc->silence_dmah) != DDI_SUCCESS) {
+ audio_dev_warn(devc->adev,
+ "failed to allocate silent page handle");
+ return (DDI_FAILURE);
+ }
+
+ if (ddi_dma_mem_alloc(devc->silence_dmah, 4096,
+ &buf_attr, DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL,
+ &devc->silence_kaddr, &len,
+ &devc->silence_acch) != DDI_SUCCESS) {
+ audio_dev_warn(devc->adev,
+ "failed to allocate silent page memory");
+ return (DDI_FAILURE);
+ }
+
+ (void) ddi_dma_sync(devc->silence_dmah, 0, 0, DDI_DMA_SYNC_FORDEV);
+
+ if (ddi_dma_addr_bind_handle(devc->silence_dmah, NULL,
+ devc->silence_kaddr, len, DDI_DMA_CONSISTENT | DDI_DMA_WRITE,
+ DDI_DMA_SLEEP, NULL, &cookie, &count) != DDI_SUCCESS) {
+ audio_dev_warn(devc->adev,
+ "failed binding silent page DMA handle");
+ return (DDI_FAILURE);
+ }
+
+ devc->silence_paddr = cookie.dmac_address;
+ bzero(devc->silence_kaddr, 4096);
+ devc->audio_memptr = 4096; /* Skip the silence page */
+
+ for (i = 0; i < devc->max_pages; i++)
+ FILL_PAGE_MAP_ENTRY(i, devc->silence_paddr);
+
+ (void) ddi_dma_sync(devc->pt_dmah, 0, 0, DDI_DMA_SYNC_FORDEV);
+
+ devc->ac97 = ac97_allocate(devc->adev, dip,
+ emu10k_read_ac97, emu10k_write_ac97, devc);
+ if (devc->ac97 == NULL) {
+ audio_dev_warn(devc->adev, "failed to allocate ac97 handle");
+ goto error;
+ }
+
+ ac97_probe_controls(devc->ac97);
+
+ /* allocate voice 0 for play */
+ if (emu10k_alloc_port(devc, EMU10K_REC) != DDI_SUCCESS)
+ goto error;
+
+ if (emu10k_alloc_port(devc, EMU10K_PLAY) != DDI_SUCCESS)
+ goto error;
+
+ /* now initialize the hardware */
+ mutex_enter(&devc->mutex);
+ if (emu10k_hwinit(devc) != DDI_SUCCESS) {
+ mutex_exit(&devc->mutex);
+ goto error;
+ }
+ mutex_exit(&devc->mutex);
+
+ emu10k_create_controls(devc);
+
+ /* set up kernel statistics */
+ if ((devc->ksp = kstat_create(EMU10K_NAME, ddi_get_instance(dip),
+ EMU10K_NAME, "controller", KSTAT_TYPE_INTR,
+ 1, KSTAT_FLAG_PERSISTENT)) != NULL) {
+ kstat_install(devc->ksp);
+ }
+
+ if (audio_dev_register(devc->adev) != DDI_SUCCESS) {
+ audio_dev_warn(devc->adev, "unable to register audio device");
+ goto error;
+ }
+
+ (void) ddi_intr_enable(devc->ih);
+ ddi_report_dev(dip);
+
+ return (DDI_SUCCESS);
+
+error:
+ emu10k_destroy(devc);
+ return (DDI_FAILURE);
+}
+
+int
+emu10k_resume(dev_info_t *dip)
+{
+ emu10k_devc_t *devc;
+ emu10k_portc_t *portc;
+
+ devc = ddi_get_driver_private(dip);
+
+ for (int i = 0; i < EMU10K_NUM_PORTC; i++) {
+ portc = devc->portc[i];
+ audio_engine_reset(portc->engine);
+ }
+
+ mutex_enter(&devc->mutex);
+ if (emu10k_hwinit(devc) != DDI_SUCCESS) {
+ mutex_exit(&devc->mutex);
+ /*
+ * In case of failure, we leave the chip suspended,
+ * but don't panic. Audio service is not normally a a
+ * critical service.
+ */
+ audio_dev_warn(devc->adev, "FAILED to RESUME device");
+ return (DDI_SUCCESS);
+ }
+
+ emu10k_refresh_mixer(devc);
+
+ devc->suspended = B_FALSE;
+
+ for (int i = 0; i < EMU10K_NUM_PORTC; i++) {
+
+ portc = devc->portc[i];
+
+ portc->stop_port(portc);
+
+ portc->dopos = 1;
+ if (portc->started) {
+ portc->reset_port(portc);
+ portc->start_port(portc);
+ }
+ }
+
+ mutex_exit(&devc->mutex);
+
+ /* resume ac97 */
+ ac97_resume(devc->ac97);
+
+ return (DDI_SUCCESS);
+}
+
+int
+emu10k_detach(emu10k_devc_t *devc)
+{
+ if (audio_dev_unregister(devc->adev) != DDI_SUCCESS)
+ return (DDI_FAILURE);
+
+ emu10k_destroy(devc);
+ return (DDI_SUCCESS);
+}
+
+int
+emu10k_suspend(emu10k_devc_t *devc)
+{
+ ac97_suspend(devc->ac97);
+
+ mutex_enter(&devc->mutex);
+
+ devc->suspended = B_TRUE;
+
+ emu10k_write_reg(devc, CLIEL, 0, 0);
+ emu10k_write_reg(devc, CLIEH, 0, 0);
+ if (!(devc->feature_mask & SB_LIVE)) {
+ emu10k_write_reg(devc, HLIEL, 0, 0x0);
+ emu10k_write_reg(devc, HLIEH, 0, 0x0);
+ }
+ OUTL(devc, 0, devc->regs + IE); /* Intr enable (all off) */
+
+ for (int i = 0; i < EMU10K_NUM_PORTC; i++) {
+ emu10k_portc_t *portc = devc->portc[i];
+ portc->stop_port(portc);
+ }
+
+ /* stop all voices */
+ for (int i = 0; i < 64; i++) {
+ emu10k_write_reg(devc, VEDS, i, 0);
+ }
+ for (int i = 0; i < 64; i++) {
+ emu10k_write_reg(devc, VTFT, i, 0);
+ emu10k_write_reg(devc, CVCF, i, 0);
+ emu10k_write_reg(devc, PTAB, i, 0);
+ emu10k_write_reg(devc, CPF, i, 0);
+ }
+ /*
+ * Turn off the hardware
+ */
+ OUTL(devc,
+ HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK |
+ HCFG_MUTEBUTTONENABLE, devc->regs + HCFG);
+
+ /* stop ADC recording */
+ emu10k_write_reg(devc, ADCSR, 0, 0x0);
+ emu10k_write_reg(devc, ADCBA, 0, 0x0);
+ emu10k_write_reg(devc, ADCBA, 0, 0x0);
+
+ emu10k_write_reg(devc, PTBA, 0, 0);
+
+ mutex_exit(&devc->mutex);
+
+ return (DDI_SUCCESS);
+}
+
+static int emu10k_ddi_attach(dev_info_t *, ddi_attach_cmd_t);
+static int emu10k_ddi_detach(dev_info_t *, ddi_detach_cmd_t);
+static int emu10k_ddi_quiesce(dev_info_t *);
+
+static struct dev_ops emu10k_dev_ops = {
+ DEVO_REV, /* rev */
+ 0, /* refcnt */
+ NULL, /* getinfo */
+ nulldev, /* identify */
+ nulldev, /* probe */
+ emu10k_ddi_attach, /* attach */
+ emu10k_ddi_detach, /* detach */
+ nodev, /* reset */
+ NULL, /* cb_ops */
+ NULL, /* bus_ops */
+ NULL, /* power */
+ emu10k_ddi_quiesce, /* quiesce */
+};
+
+static struct modldrv emu10k_modldrv = {
+ &mod_driverops, /* drv_modops */
+ "Creative EMU10K Audio", /* linkinfo */
+ &emu10k_dev_ops, /* dev_ops */
+};
+
+static struct modlinkage modlinkage = {
+ MODREV_1,
+ { &emu10k_modldrv, NULL }
+};
+
+int
+_init(void)
+{
+ int rv;
+
+ audio_init_ops(&emu10k_dev_ops, EMU10K_NAME);
+ if ((rv = mod_install(&modlinkage)) != 0) {
+ audio_fini_ops(&emu10k_dev_ops);
+ }
+ return (rv);
+}
+
+int
+_fini(void)
+{
+ int rv;
+
+ if ((rv = mod_remove(&modlinkage)) == 0) {
+ audio_fini_ops(&emu10k_dev_ops);
+ }
+ return (rv);
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+ return (mod_info(&modlinkage, modinfop));
+}
+
+int
+emu10k_ddi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+ switch (cmd) {
+ case DDI_ATTACH:
+ return (emu10k_attach(dip));
+
+ case DDI_RESUME:
+ return (emu10k_resume(dip));
+
+ default:
+ return (DDI_FAILURE);
+ }
+}
+
+int
+emu10k_ddi_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+ emu10k_devc_t *devc;
+
+ devc = ddi_get_driver_private(dip);
+
+ switch (cmd) {
+ case DDI_DETACH:
+ return (emu10k_detach(devc));
+
+ case DDI_SUSPEND:
+ return (emu10k_suspend(devc));
+
+ default:
+ return (DDI_FAILURE);
+ }
+}
+
+int
+emu10k_ddi_quiesce(dev_info_t *dip)
+{
+ emu10k_devc_t *devc;
+
+ devc = ddi_get_driver_private(dip);
+
+ /* stop all voices */
+ for (int i = 0; i < 64; i++) {
+ emu10k_write_reg(devc, VEDS, i, 0);
+ }
+ for (int i = 0; i < 64; i++) {
+ emu10k_write_reg(devc, VTFT, i, 0);
+ emu10k_write_reg(devc, CVCF, i, 0);
+ emu10k_write_reg(devc, PTAB, i, 0);
+ emu10k_write_reg(devc, CPF, i, 0);
+ }
+
+ /*
+ * Turn off the hardware
+ */
+ OUTL(devc, 0, devc->regs + IE); /* Intr enable (all off) */
+ OUTL(devc,
+ HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK |
+ HCFG_MUTEBUTTONENABLE, devc->regs + HCFG);
+
+ /* stop ADC recording */
+ emu10k_write_reg(devc, ADCSR, 0, 0x0);
+ emu10k_write_reg(devc, ADCBA, 0, 0x0);
+ emu10k_write_reg(devc, ADCBA, 0, 0x0);
+
+ emu10k_write_reg(devc, PTBA, 0, 0);
+
+ return (DDI_SUCCESS);
+}
diff --git a/usr/src/uts/common/io/audio/drv/audioemu10k/audioemu10k.h b/usr/src/uts/common/io/audio/drv/audioemu10k/audioemu10k.h
new file mode 100644
index 0000000000..d7f47497d7
--- /dev/null
+++ b/usr/src/uts/common/io/audio/drv/audioemu10k/audioemu10k.h
@@ -0,0 +1,453 @@
+/*
+ * 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 SB Live/Audigy driver
+ */
+/*
+ * Copyright (C) 4Front Technologies 1996-2009.
+ */
+#ifndef EMU10K_H
+#define EMU10K_H
+
+#define PCI_VENDOR_ID_CREATIVE 0x1102
+#define PCI_DEVICE_ID_SBLIVE 0x0002
+#define PCI_DEVICE_ID_AUDIGY 0x0004
+#define PCI_DEVICE_ID_AUDIGYVALUE 0x0008
+
+#define SAMPLE_RATE 48000
+
+#define EMU10K_NAME "audioemu10k"
+
+#define EMU10K_NUM_PORTC 2
+#define EMU10K_PLAY 0
+#define EMU10K_REC 1
+
+#define EMU10K_NUM_FRAGS (2*4) /* Must be multiple of 2 */
+
+#define EMU10K_MAX_INTRS 512
+#define EMU10K_MIN_INTRS 10
+#define EMU10K_INTRS 100
+
+#define FRAGMENT_FRAMES 512
+
+#define EMU10K1_MAGIC 0xe10001
+#define EMU10K2_MAGIC 0xe10002
+
+/* Audio */
+
+#define DMABUF_SIZE (256 * 1024)
+
+#define AUDIO_MAXVOICE (2*EMU10K_NUM_PORTC)
+/* Audio buffer + silent page */
+#define AUDIO_MEMSIZE (EMU10K_NUM_PORTC*DMABUF_SIZE+4096)
+
+/* Wall clock register */
+#define WC 0x10
+
+/* Hardware config register */
+#define HCFG 0x14
+#define HCFG_CODECFORMAT_MASK 0x00070000 /* CODEC format */
+#define HCFG_CODECFORMAT_AC97 0x00000000 /* AC97 CODEC format */
+#define HCFG_CODECFORMAT_I2S 0x00010000 /* I2S CODEC format */
+#define HCFG_GPINPUT0 0x00004000 /* External pin112 */
+#define HCFG_GPINPUT1 0x00002000 /* External pin110 */
+#define HCFG_GPOUTPUT_MASK 0x00001c00 /* Controllable pins */
+#define HCFG_GPOUT0 0x00001000 /* enable dig out on 5.1 */
+#define HCFG_GPOUT1 0x00000800 /* IR */
+#define HCFG_GPOUT2 0x00000400 /* IR */
+#define HCFG_JOYENABLE 0x00000200 /* Internal joystick enable */
+#define HCFG_PHASETRACKENABLE 0x00000100 /* Phase tracking enable */
+#define HCFG_AC3ENABLE_MASK 0x0x0000e0 /* AC3 async input control */
+#define HCFG_AC3ENABLE_ZVIDEO 0x00000080 /* Chan 0/1 replace ZVIDEO */
+#define HCFG_AC3ENABLE_CDSPDIF 0x00000040 /* Chan 0/1 replace CDSPDIF */
+#define HCFG_AC3ENABLE_GPSPDIF 0x00000020 /* Chan 0/1 replace GPSPDIF */
+#define HCFG_AUTOMUTE 0x00000010
+#define HCFG_LOCKSOUNDCACHE 0x00000008
+#define HCFG_LOCKTANKCACHE_MASK 0x00000004
+#define HCFG_LOCKTANKCACHE 0x01020014
+#define HCFG_MUTEBUTTONENABLE 0x00000002 /* Mute can clear audioenable */
+#define HCFG_AUDIOENABLE 0x00000001 /* Codecs can send data */
+#define A_HCFG_VMUTE 0x00004000
+#define A_HCFG_AUTOMUTE 0x00008000
+#define A_HCFG_XM 0x00040000 /* Xtended address mode */
+
+/*
+ * GPIO bit definitions (global register 0x18) for Audigy.
+ */
+
+#define A_IOCFG_GPOUT0 0x0044 /* analog/digital? */
+#define A_IOCFG_GPOUT1 0x0002 /* IR */
+#define A_IOCFG_GPOUT2 0x0001 /* IR */
+
+/* Status bits (read only) */
+#define GPIO_VERSAPLUGGED 0x2000 /* Center/LFE/digital */
+#define GPIO_FRONTPLUGGED 0x4000
+#define GPIO_REARPLUGGED 0x8000
+#define GPIO_HEADPHPLUGGED 0x0100
+#define GPIO_ANALOG_MUTE 0x0040
+#define GPIO_DIGITAL_ENABLE 0x0004 /* Cen/lfe (0) or digital (1) switch */
+
+#define FILL_PAGE_MAP_ENTRY(e, v) \
+ ddi_put32(devc->pt_acch, devc->page_map + e, ((v) << 1) | (e));
+
+/*
+ * Audio block registers
+ */
+
+#define CPF 0x000 /* DW:cnl Current pitch and fraction */
+#define CPF_CURRENTPITCH_MASK 0xffff0000
+#define CPF_CURRENTPITCH 0x10100000
+#define CPF_STEREO_MASK 0x00008000
+#define CPF_STOP_MASK 0x00004000
+#define CPF_FRACADDRESS_MASK 0x00003fff
+
+
+#define PTAB 0x001 /* DW:cnl Pitch target and sends A and B */
+#define PTRX_PITCHTARGET_MASK 0xffff0000
+#define PTRX_PITCHTARGET 0x10100001
+#define PTRX_FXSENDAMOUNT_A_MASK 0x0000ff00
+#define PTRX_FXSENDAMOUNT_A 0x08080001
+#define PTRX_FXSENDAMOUNT_B_MASK 0x000000ff
+#define PTRX_FXSENDAMOUNT_B 0x08000001
+
+
+#define CVCF 0x002 /* DW:cnl Curr vol and curr filter cutoff */
+#define VTFT 0x003 /* DW:cnl Volume tgt and filter cutoff tgt */
+#define Z2 0x004 /* DW:cnl Filter delay memory 2 */
+#define Z1 0x005 /* DW:cnl Filter delay memory 1 */
+#define SCSA 0x006 /* DW:cnl Send C and Start addr */
+#define SDL 0x007 /* DW:cnl Send D and Loop addr */
+#define QKBCA 0x008 /* DW:cnl Filter Q, ROM, etc */
+#define CCR 0x009
+#define CCR_CACHEINVALIDSIZE 0x07190009
+#define CCR_CACHEINVALIDSIZE_MASK 0xfe000000
+#define CCR_CACHELOOPFLAG 0x01000000
+#define CCR_INTERLEAVEDSAMPLES 0x00800000
+#define CCR_WORDSIZEDSAMPLES 0x00400000
+#define CCR_READADDRESS 0x06100009
+#define CCR_READADDRESS_MASK 0x003f0000
+#define CCR_LOOPINVALSIZE 0x0000fe00
+#define CCR_LOOPFLAG 0x00000100
+#define CCR_CACHELOOPADDRHI 0x000000ff
+
+#define CLP 0x00a
+#define SRHE 0x07c
+#define STHE 0x07d
+#define SRDA 0x07e
+#define STDA 0x07f
+#define L_FXRT 0x00b
+#define FXRT 0x00b /* W:cnl */
+#define MAPA 0x00c
+#define MAPB 0x00d
+#define VEV 0x010 /* W:cnl */
+#define VEHA 0x011 /* W:cnl */
+#define VEDS 0x012 /* W:cnl */
+#define MLV 0x013 /* W:cnl */
+#define MEV 0x014 /* W:cnl */
+#define MEHA 0x015 /* W:cnl */
+#define MEDS 0x016 /* W:cnl */
+#define VLV 0x017 /* W:cnl */
+#define IP 0x018 /* W:cnl */
+#define IFA 0x019 /* W:cnl */
+#define PEFE 0x01a /* W:cnl */
+#define PEFE_PITCHAMOUNT_MASK 0x0000ff00 /* Pitch envlope amount */
+#define PEFE_PITCHAMOUNT 0x0808001a
+#define PEFE_FILTERAMOUNT_MASK 0x000000ff /* Filter envlope amount */
+#define PEFE_FILTERAMOUNT 0x0800001a
+
+#define VFM 0x01b /* W:cnl */
+#define TMFQ 0x01c /* W:cnl */
+#define VVFQ 0x01d /* W:cnl */
+#define TMPE 0x01e /* W:cnl */
+#define CD0 0x020 /* DW:cnl (16 registers) */
+#define PTBA 0x040 /* DW:nocnl */
+#define TCBA 0x041 /* DW:nocnl */
+#define ADCSR 0x042 /* B:nocnl */
+#define FXWC 0x043 /* DW:nocnl */
+#define TCBS 0x044 /* B:nocnl */
+#define MBA 0x045 /* DW:nocnl */
+#define ADCBA 0x046 /* DW:nocnl */
+#define FXBA 0x047 /* DW:nocnl */
+
+#define MBS 0x049 /* B:nocnl */
+#define ADCBS 0x04a /* B:nocnl */
+#define FXBS 0x04b /* B:nocnl */
+#define CSBA 0x4c
+#define CSDC 0x4d
+#define CSFE 0x4e
+#define CSHG 0x4f
+#define CDCS 0x050 /* DW:nocnl */
+#define GPSCS 0x051 /* DW:nocnl */
+#define DBG 0x052 /* DW:nocnl */
+#define AUDIGY_DBG 0x053 /* DW:nocnl */
+#define SCS0 0x054 /* DW:nocnl */
+#define SCS1 0x055 /* DW:nocnl */
+#define SCS2 0x056 /* DW:nocnl */
+#define CLIEL 0x058 /* DW:nocnl */
+#define CLIEH 0x059 /* DW:nocnl */
+#define CLIPL 0x05a /* DW:nocnl */
+#define CLIPH 0x05b /* DW:nocnl */
+#define SOLL 0x05c /* DW:nocnl */
+#define SOLH 0x05d /* DW:nocnl */
+#define SOC 0x05e /* DW:nocnl */
+#define AC97SLOT 0x05f
+#define AC97SLOT_REAR_RIGHT 0x01
+#define AC97SLOT_REAR_LEFT 0x02
+#define AC97SLOT_CENTER 0x10
+#define AC97SLOT_LFE 0x20
+#define CDSRCS 0x060 /* DW:nocnl */
+#define GPSRCS 0x061 /* DW:nocnl */
+#define ZVSRCS 0x062 /* DW:nocnl */
+#define ADCIDX 0x063 /* W:nocnl */
+#define MIDX 0x064 /* W:nocnl */
+#define FXIDX 0x065 /* W:nocnl */
+
+/* Half loop interrupt registers (audigy only) */
+#define HLIEL 0x066 /* DW:nocnl */
+#define HLIEH 0x067 /* DW:nocnl */
+#define HLIPL 0x068 /* DW:nocnl */
+#define HLIPH 0x069 /* DW:nocnl */
+#define GPR0 ((devc->feature_mask&SB_LIVE)? 0x100:0x400) /* DW:nocnl */
+#define TMA0 0x300 /* Tank memory */
+#define UC0 ((devc->feature_mask&SB_LIVE) ? 0x400:0x600) /* DSM ucode */
+
+/* Interrupt pending register */
+#define INTPEND 0x08
+#define INT_VI 0x00100000
+#define INT_VD 0x00080000
+#define INT_MU 0x00040000
+#define INT_MF 0x00020000
+#define INT_MH 0x00010000
+#define INT_AF 0x00008000
+#define INT_AH 0x00004000
+#define INT_IT 0x00000200
+#define INT_TX 0x00000100
+#define INT_RX 0x00000080
+#define INT_CL 0x00000040
+/* Interrupt enable register */
+#define IE 0x0c
+#define IE_VI 0x00000400
+#define IE_VD 0x00000200
+#define IE_MU 0x00000100
+#define IE_MB 0x00000080
+#define IE_AB 0x00000040
+#define IE_IT 0x00000004
+#define IE_TX 0x00000002
+#define IE_RX 0x00000001
+
+/* Interval timer register */
+#define TIMR 0x1a
+
+/* EMU10K2 MIDI UART */
+#define MUADAT 0x070
+#define MUACMD 0x071
+#define MUASTAT MUACMD
+
+/* EMU10K2 S/PDIF recording buffer */
+#define SPRI 0x6a
+#define SPRA 0x6b
+#define SPRC 0x6c
+
+#define EHC 0x76 /* Audigy 2 */
+
+#define SRHE 0x07c
+#define STHE 0x07d
+#define SRDA 0x07e
+
+#define ROM0 0x00000000 /* interpolation ROM 0 */
+#define ROM1 0x02000000 /* interpolation ROM 1 */
+#define ROM2 0x04000000 /* interpolation ROM 2 */
+#define ROM3 0x06000000 /* interpolation ROM 3 */
+#define ROM4 0x08000000 /* interpolation ROM 4 */
+#define ROM5 0x0A000000 /* interpolation ROM 5 */
+#define ROM6 0x0C000000 /* interpolation ROM 6 */
+#define ROM7 0x0E000000 /* interpolation ROM 7 */
+#define BYTESIZE 0x01000000 /* byte sound memory */
+
+#define MAX_GPR 256
+
+/* See feature_mask below */
+#define SB_LIVE 1
+#define SB_AUDIGY 2
+#define SB_AUDIGY2 4
+#define SB_AUDIGY2VAL 8
+#define SB_51 0x10
+#define SB_71 0x20
+#define SB_INVSP 0x40 /* invert shared spdif switch */
+#define SB_NOEXP 0x80 /* no support for Live! Drive or expansion */
+
+#define LEFT_CH 0
+#define RIGHT_CH 1
+
+#ifdef _KERNEL
+
+typedef struct _emu10k_devc_t emu10k_devc_t;
+typedef struct _emu10k_portc_t emu10k_portc_t;
+
+
+typedef enum {
+ CTL_VOLUME = 0,
+ CTL_FRONT,
+ CTL_SURROUND,
+ CTL_CENTER,
+ CTL_LFE,
+ CTL_SIDE,
+ CTL_HEADPH,
+
+ CTL_RECGAIN,
+ CTL_RECSRC,
+ CTL_AC97SRC,
+
+ /* monitor source values */
+ CTL_AC97,
+ CTL_DIGCD,
+ CTL_SPD1,
+ CTL_SPD2,
+ CTL_LINE2,
+ CTL_AUX2,
+
+ CTL_JACK3,
+
+ /* this one must be last */
+ CTL_MAX,
+} emu10k_ctrl_id_t;
+
+typedef struct _emu10k_ctrl {
+ emu10k_devc_t *devc;
+ audio_ctrl_t *ctrl;
+ int gpr_num;
+ uint64_t val;
+} emu10k_ctrl_t;
+
+typedef struct _emu10k_gpr {
+ boolean_t valid;
+ uint32_t value;
+} emu10k_gpr_t;
+
+struct _emu10k_portc_t {
+ emu10k_devc_t *devc;
+ audio_engine_t *engine;
+
+ /* Helper functions */
+ void (*update_port)(emu10k_portc_t *);
+ void (*reset_port)(emu10k_portc_t *);
+ void (*stop_port)(emu10k_portc_t *);
+ void (*start_port)(emu10k_portc_t *);
+
+ int channels;
+
+ boolean_t started;
+ boolean_t active;
+ unsigned fragfr;
+ unsigned nframes;
+ unsigned nfrags;
+ unsigned fragsz;
+
+ ddi_dma_handle_t buf_dmah; /* dma for buffers */
+ ddi_acc_handle_t buf_acch;
+ uint32_t buf_paddr;
+ caddr_t buf_kaddr;
+ size_t buf_size;
+ /* Start of loop within the internal memory space */
+ uint32_t memptr;
+ int syncdir;
+ /* Position & timing */
+ uint64_t count;
+ uint32_t pos;
+ int dopos;
+};
+
+struct _emu10k_devc_t {
+ dev_info_t *dip;
+ audio_dev_t *adev;
+ kstat_t *ksp;
+ boolean_t suspended;
+ ddi_acc_handle_t pcih;
+ ddi_acc_handle_t regsh;
+ caddr_t regs;
+ kmutex_t mutex;
+ ddi_intr_handle_t ih;
+
+ /*
+ * Page table
+ */
+ ddi_dma_handle_t pt_dmah; /* dma for page_tablefers */
+ ddi_acc_handle_t pt_acch;
+ uint32_t pt_paddr;
+ caddr_t pt_kaddr;
+ uint32_t *page_map; /* up to 8k ptrs to 4k pages */
+
+
+ /*
+ * Silent page used by voices that don't play anything.
+ */
+ ddi_dma_handle_t silence_dmah; /* dma for silencefers */
+ ddi_acc_handle_t silence_acch;
+ uint32_t silence_paddr;
+ caddr_t silence_kaddr;
+
+ /*
+ * Device feature mask tells which kind of features are
+ * supported by the hardware. Audigy2/2val have multiple bits
+ * set while Live! has just the SB_LIVE bits. So Features of
+ * Audigy will be reported by Audigy2/val too.
+ */
+ int feature_mask;
+ int max_mem, max_pages, nr_pages;
+ /*
+ * Mixer
+ */
+ ac97_t *ac97;
+ ac97_ctrl_t *ac97_recsrc;
+ uint32_t ac97_stereomix;
+ emu10k_gpr_t gpr_shadow[MAX_GPR];
+ emu10k_ctrl_t ctrls[CTL_MAX];
+
+ /*
+ * Audio
+ */
+
+ int audio_memptr;
+ int *silent_page;
+
+ emu10k_portc_t *portc[EMU10K_NUM_PORTC];
+};
+
+#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))
+
+#define EMU10K_KIOP(X) ((kstat_intr_t *)(X->ksp->ks_data))
+
+#endif /* _KERNEL */
+
+#endif /* EMU10K_H */
diff --git a/usr/src/uts/common/io/audio/drv/audioemu10k/dsp/asm10k.c b/usr/src/uts/common/io/audio/drv/audioemu10k/dsp/asm10k.c
new file mode 100644
index 0000000000..4d783bc906
--- /dev/null
+++ b/usr/src/uts/common/io/audio/drv/audioemu10k/dsp/asm10k.c
@@ -0,0 +1,1096 @@
+/*
+ * 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.
+ */
+
+/*
+ * Assembler for Emu10k1
+ */
+/*
+ * Copyright (C) 4Front Technologies 1996-2008.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <sys/param.h>
+
+#include <sys/soundcard.h>
+
+#define MAX_GPR 256
+#define MAX_GPR_PARMS 60
+#define MAX_CONST_PARMS 128
+#define GPR_NAME_SIZE 32
+
+typedef struct {
+ char name[GPR_NAME_SIZE];
+ unsigned int num;
+ int type;
+ int def;
+} gpr_t;
+
+typedef struct {
+ unsigned int gpr;
+ unsigned int value;
+} const_t;
+
+typedef struct {
+ unsigned int ngpr;
+
+ gpr_t gpr[MAX_GPR_PARMS];
+} gpr_info;
+
+typedef struct {
+ unsigned int nconst;
+
+ const_t consts[MAX_CONST_PARMS];
+} const_info;
+
+typedef struct {
+ unsigned int code[1024];
+ gpr_info parms;
+ const_info consts;
+ int ninit;
+ struct {
+ uint32_t gpr;
+ uint32_t value;
+ char name[GPR_NAME_SIZE];
+ } init[MAX_GPR];
+} emu10k1_file;
+
+#define MAX_NAME 64
+#define MAX_SYMBOLS 1024
+
+static int parms_only = 0;
+static int is_audigy = 0;
+static int verbose = 0;
+
+static int gpr_base = 0x100;
+static int input_base = 0x10;
+static int output_base = 0x20;
+
+static char *progname;
+
+typedef struct {
+ char name[MAX_NAME];
+ int type;
+#define SY_DUMMY 0
+#define SY_GPR 1
+#define SY_INPUT 2
+#define SY_OUTPUT 3
+#define SY_CONST 4
+#define SY_FX 5
+#define SY_ACCUM 6
+#define SY_PARM 7
+ int arg;
+} sym_t;
+
+typedef struct {
+ char *name;
+ int opcode;
+} instruction_t;
+
+static char remarks[2048] = "";
+static char *banner =
+ "/*\n"
+ " * Note: This file was automatically generated by %s\n"
+ " * on %s.\n"
+ " */\n";
+
+/*
+ * Instructions. Each instruction takes 4 arguments, R, A, X, and Y.
+ */
+static instruction_t instructions[] = {
+ { "MACS", 0x0}, /* R = A + (X * Y >> 31); saturation */
+ { "MACS1", 0x1}, /* R = A + (-X * Y >> 31); saturation */
+ { "MACW", 0x2}, /* R = A + (X * Y >> 31); wraparound */
+ { "MACW1", 0x3}, /* R = A + (-X * Y >> 31); wraparound */
+ { "MACINTS", 0x4}, /* R = A + (X * Y); saturation */
+ { "MACINTW", 0x5}, /* R = A + (X * Y); wraparound */
+ { "SUM", 0x6}, /* R = A + X + Y; saturation */
+ { "ACC3", 0x6}, /* R = A + X + Y; saturation */
+ { "MACMV", 0x7}, /* R = A, acc += X * Y >> 31 */
+ { "ANDXOR", 0x8}, /* R = (A & X) ^ Y */
+ { "TSTNEG", 0x9}, /* R = (A >= Y) ? X : ~X */
+ { "LIMIT", 0xa}, /* R = (A >= Y) ? X : Y */
+ { "LIMIT1", 0xb}, /* R = (A < Y) ? X : Y */
+ { "LOG", 0xc}, /* R = ... (log?) */
+ { "EXP", 0xd}, /* R = ... (exp?) */
+ { "INTERP", 0xe}, /* R = A + (X * (Y - A) >> 31) */
+ { "SKIP", 0xf}, /* R, CCR, CC_TEST, COUNT */
+ { NULL, 0}
+};
+
+#define CHECK_COUNT(tokens, cnt, mincnt, maxcnt) \
+ if (cnt < mincnt) { \
+ error("Too few parameters for '%s' (have %d, min %d)", \
+ tokens[0], cnt - 1, mincnt - 1); \
+ return; \
+ } \
+ if (cnt > maxcnt) { \
+ error("Too many parameters for '%s' (have %d, max %d)", \
+ tokens[0], cnt - 1, maxcnt - 1); \
+ return; \
+ }
+
+static sym_t symtab[MAX_SYMBOLS];
+static int nsyms = 0;
+
+static int lineno = 0, errors = 0;
+static emu10k1_file fle;
+static int pc;
+
+static int ngpr = 0;
+static char *infile;
+
+static int
+getline(FILE *input, char **tokens)
+{
+ char *s, *ls;
+ static char *stmt = NULL, *lasts = NULL;
+ static char line[4096];
+ int cnt, tokcnt;
+
+ for (;;) {
+
+ if (stmt == NULL) {
+ if (fgets(line, sizeof (line), input) == NULL)
+ return (-1);
+ lineno++;
+
+ /*
+ * Special handling for .' comments. We use
+ * .' as a keyword to ensure that entire
+ * comment makes it through the C preprocessor
+ * unmolested. We also need to make sure *we*
+ * don't molest it either. The comment will
+ * be exported to any resulting header,
+ * allowing us to pass through copyright and
+ * other information from the source file to
+ * the resulting header.
+ */
+ s = line;
+ s += strspn(s, " \t");
+ if ((strncmp(s, ".'", 2) == 0) &&
+ (strchr(" \t\n", s[2]) != NULL)) {
+ /* chop off trailing new line */
+ (void) strtok(line, "\n");
+ tokens[0] = s;
+ s += 2;
+ s += strspn(s, " \t");
+ if ((s[0] == '\'') &&
+ (s[strlen(s) - 1] == '\'')) {
+ s[strlen(s) - 1] = 0;
+ s++;
+ }
+ tokens[1] = s;
+ tokens[0][2] = 0;
+ tokens[2] = NULL;
+ stmt = NULL;
+ return (strlen(tokens[1]) ? 2 : 1);
+ }
+
+ /* strip off any C++ style comments that CPP missed */
+ if ((s = strstr(line, "//")) != NULL) {
+ *s = NULL;
+ }
+ stmt = strtok_r(line, ";\n", &lasts);
+ } else {
+ stmt = strtok_r(NULL, ";\n", &lasts);
+ }
+
+ if (stmt != NULL) {
+ break;
+ }
+ }
+
+ /*
+ * Ok, we have a statement, lets tokenize it. For
+ * simplicities sake we convert "OPCODE(arg1, arg2)" into
+ * "OPCODE arg1 arg2". This means that commas and parens are
+ * treated as whitespace. This can lead to some really messed
+ * up syntaxes that get assembled properly (such as nested
+ * calls, empty arguments, etc.) Hopefully people don't abuse
+ * this.
+ */
+ ls = NULL;
+ s = strtok_r(stmt, " \t\n(),", &ls);
+ cnt = 0;
+ tokcnt = 0;
+ while (cnt < 10) {
+ tokens[cnt++] = s;
+ if (s != NULL) {
+ tokcnt++;
+ s = strtok_r(NULL, " \t\n(),", &ls);
+ }
+ }
+ return (tokcnt);
+}
+
+static void
+error(char *msg, ...)
+{
+ va_list va;
+ char msgbuf[1024];
+
+ va_start(va, msg);
+ (void) vsnprintf(msgbuf, sizeof (msgbuf), msg, va);
+ va_end(va);
+
+ (void) fprintf(stderr, "Error: %s on line %d of %s\n", msgbuf, lineno,
+ infile);
+ errors++;
+}
+
+static sym_t *
+find_symbol(char *name)
+{
+ int i;
+
+ for (i = 0; i < nsyms; i++)
+ if (strcmp(symtab[i].name, name) == 0) {
+ return (&symtab[i]);
+ }
+
+ return (NULL);
+}
+
+static void
+add_symbol(char *name, int type, int arg)
+{
+ sym_t *sym;
+
+ if (nsyms >= MAX_SYMBOLS) {
+ error("Symbol table full");
+ exit(-1);
+ }
+
+ if (find_symbol(name) != NULL) {
+ error("Dublicate symbol '%s'", name);
+ return;
+ }
+
+ if (strlen(name) >= MAX_NAME) {
+ error("Symbol name '%s' too long", name);
+ exit(-1);
+ }
+
+ sym = &symtab[nsyms++];
+
+ (void) strcpy(sym->name, name);
+ sym->type = type;
+ sym->arg = arg;
+}
+
+static void
+add_init(uint32_t gpr, uint32_t val, const char *name)
+{
+ int n;
+
+ n = fle.ninit;
+ if (n >= MAX_GPR) {
+ error("Too many GPRs");
+ return;
+ }
+ fle.init[n].gpr = gpr;
+ fle.init[n].value = val;
+ if (name)
+ (void) strlcpy(fle.init[n].name, name,
+ sizeof (fle.init[n].name));
+ fle.ninit++;
+}
+
+static void
+compile_gpr(char **tokens, int cnt)
+{
+ CHECK_COUNT(tokens, cnt, 2, 2);
+
+ if (ngpr >= MAX_GPR)
+ error("Too many GPR variables");
+
+ add_symbol(tokens[1], SY_GPR, gpr_base + ngpr++);
+}
+
+static void
+compile_rem(char **tokens, int cnt)
+{
+ int i;
+
+ (void) strlcat(remarks, " *", sizeof (remarks));
+ for (i = 1; i < cnt; i++) {
+ (void) strlcat(remarks, " ", sizeof (remarks));
+ (void) strlcat(remarks, tokens[i], sizeof (remarks));
+ }
+ (void) strlcat(remarks, "\n", sizeof (remarks));
+}
+
+static void
+declare_const(unsigned int gpr, char *value)
+{
+ int n, intv;
+ float v;
+
+ n = fle.consts.nconst;
+
+ if (n >= MAX_CONST_PARMS) {
+ error("Too many constant parameters");
+ return;
+ }
+
+ if (*value == 'I') {
+ if (sscanf(&value[1], "%g", &v) != 1) {
+ error("Bad floating point value (%s)", value);
+ return;
+ }
+ intv = (int)v;
+ } else if (*value == '0' && value[1] == 'x') {
+ if (sscanf(&value[2], "%x", (unsigned *)&intv) != 1) {
+ error("Bad hexadecimal value (%s)", value);
+ return;
+ }
+ } else {
+ if (sscanf(value, "%g", &v) != 1) {
+ error("Bad floating point value (%s)", value);
+ return;
+ }
+ intv = (int)(v * 0x7fffffff);
+ }
+
+ fle.consts.consts[n].gpr = gpr;
+ fle.consts.consts[n].value = intv;
+ fle.consts.nconst = n + 1;
+
+ add_init(gpr, intv, NULL);
+}
+
+static void
+compile_const(char **tokens, int cnt)
+{
+ CHECK_COUNT(tokens, cnt, 2, 3);
+ char *name = tokens[1];
+ char *value = tokens[2] ? tokens[2] : tokens[1];
+
+ if (ngpr >= MAX_GPR)
+ error("Too many GPR variables");
+
+ declare_const(ngpr, value);
+
+ add_symbol(name, SY_GPR, gpr_base + ngpr++);
+}
+
+static void
+compile_bool(char **tokens, int cnt)
+{
+ char *parm, *def;
+ int n, num;
+
+ CHECK_COUNT(tokens, cnt, 3, 3);
+
+ parm = tokens[1];
+ def = tokens[2];
+
+ n = fle.parms.ngpr;
+ if (n >= MAX_GPR_PARMS) {
+ error("Too many GPR parameters");
+ return;
+ }
+
+ if (sscanf(def, "%d", &num) != 1) {
+ error("Bad integer value near '%s'", def);
+ return;
+ }
+
+ (void) strcpy(fle.parms.gpr[n].name, parm);
+ fle.parms.gpr[n].num = ngpr;
+ fle.parms.gpr[n].def = num;
+ fle.parms.ngpr = n + 1;
+
+ add_init(ngpr, num, parm);
+
+ add_symbol(parm, SY_PARM, gpr_base + ngpr++);
+}
+
+static void
+compile_mono(char **tokens, int cnt)
+{
+ char *parm, *def;
+ int n, num;
+ char tmp[128];
+
+ CHECK_COUNT(tokens, cnt, 3, 3);
+
+ parm = tokens[1];
+ def = tokens[2];
+
+ n = fle.parms.ngpr;
+ if (n >= MAX_GPR_PARMS) {
+ error("Too many GPR parameters");
+ return;
+ }
+
+ if (sscanf(def, "%d", &num) != 1) {
+ error("Bad integer value near '%s'", def);
+ return;
+ }
+
+ (void) strcpy(fle.parms.gpr[n].name, parm);
+ fle.parms.gpr[n].num = ngpr;
+ fle.parms.gpr[n].def = num;
+ fle.parms.ngpr = n + 1;
+
+ add_init(ngpr, num, parm);
+
+ add_symbol(parm, SY_PARM, gpr_base + ngpr++);
+}
+
+static void
+compile_stereo(char **tokens, int cnt)
+{
+ char *parm, *def;
+ int n, num;
+ char tmp[128];
+
+ CHECK_COUNT(tokens, cnt, 3, 3);
+
+ parm = tokens[1];
+ def = tokens[2];
+
+ n = fle.parms.ngpr;
+ if (n >= MAX_GPR_PARMS) {
+ error("Too many GPR parameters");
+ return;
+ }
+
+ if (sscanf(def, "%d", &num) != 1) {
+ error("Bad integer value near '%s'", def);
+ return;
+ }
+
+ (void) strcpy(fle.parms.gpr[n].name, parm);
+ fle.parms.gpr[n].num = ngpr;
+ fle.parms.gpr[n].def = num | (num << 8);
+ fle.parms.ngpr = n + 1;
+
+ add_init(ngpr, num, parm);
+ add_init(ngpr + 1, num, NULL);
+
+ (void) sprintf(tmp, "%s_L", parm);
+ add_symbol(tmp, SY_PARM, gpr_base + ngpr++);
+ (void) sprintf(tmp, "%s_R", parm);
+ add_symbol(tmp, SY_PARM, gpr_base + ngpr++);
+}
+
+static void
+compile_input(char **tokens, int cnt)
+{
+ int num;
+
+ CHECK_COUNT(tokens, cnt, 3, 3);
+
+ if (sscanf(tokens[2], "%d", &num) != 1) {
+ error("Bad integer value near '%s'", tokens[2]);
+ return;
+ }
+
+ add_symbol(tokens[1], SY_INPUT, input_base + num);
+}
+
+static void
+compile_send(char **tokens, int cnt)
+{
+ int num;
+
+ CHECK_COUNT(tokens, cnt, 3, 3);
+
+ if (sscanf(tokens[2], "%d", &num) != 1) {
+ error("Bad integer near '%s'", tokens[2]);
+ return;
+ }
+
+ add_symbol(tokens[1], SY_FX, num);
+}
+
+static void
+compile_output(char **tokens, int cnt)
+{
+ int num;
+
+ CHECK_COUNT(tokens, cnt, 3, 3);
+
+ if (sscanf(tokens[2], "%d", &num) != 1) {
+ error("Bad integer value near '%s'", tokens[2]);
+ return;
+ }
+
+ add_symbol(tokens[1], SY_OUTPUT, output_base + num);
+}
+
+static void
+compile_directive(char **tokens, int cnt)
+{
+ if (strcmp(tokens[0], ".gpr") == 0) {
+ compile_gpr(tokens, cnt);
+ return;
+ }
+
+ if (strcmp(tokens[0], ".const") == 0) {
+ compile_const(tokens, cnt);
+ return;
+ }
+
+ if (strcmp(tokens[0], ".stereo") == 0) {
+ compile_stereo(tokens, cnt);
+ return;
+ }
+
+ if (strcmp(tokens[0], ".mono") == 0) {
+ compile_mono(tokens, cnt);
+ return;
+ }
+
+ if (strcmp(tokens[0], ".bool") == 0) {
+ compile_bool(tokens, cnt);
+ return;
+ }
+
+ if (strcmp(tokens[0], ".input") == 0) {
+ compile_input(tokens, cnt);
+ return;
+ }
+
+ if (strcmp(tokens[0], ".send") == 0) {
+ compile_send(tokens, cnt);
+ return;
+ }
+
+ if (strcmp(tokens[0], ".output") == 0) {
+ compile_output(tokens, cnt);
+ return;
+ }
+
+ if (strcmp(tokens[0], ".rem") == 0) {
+ compile_rem(tokens, cnt);
+ return;
+ }
+ if (strcmp(tokens[0], ".'") == 0) {
+ compile_rem(tokens, cnt);
+ return;
+ }
+
+ error("Unknown directive '%s'", tokens[0]);
+}
+
+static void
+compile_asm(char **tokens, int cnt)
+{
+ char *parms[4];
+ sym_t *symbols[4];
+#define EMIT(o, r, a, x, y) \
+ fle.code[pc*2] = ((x) << 10) | (y); \
+ fle.code[pc*2+1] = ((o) << 20) | ((r) << 10) | a; pc++
+#define EMIT_AUDIGY(o, r, a, x, y) \
+ fle.code[pc*2] = ((x) << 12) | (y); \
+ fle.code[pc*2+1] = ((o) << 24) | ((r) << 12) | a; pc++
+
+ int i, n = 0, nerr = 0;
+ int ninputs = 0;
+
+ CHECK_COUNT(tokens, cnt, 5, 5);
+
+ for (i = 0; i < 4; i++) {
+ if ((symbols[i] = find_symbol(tokens[i+1])) == NULL) {
+ (void) fprintf(stderr, "%s\n", tokens[i+1]);
+ nerr++;
+ error("Undefined symbol '%s'", tokens[i + 1]);
+ continue;
+ }
+
+ if (symbols[i]->type == SY_INPUT)
+ ninputs++;
+
+ if (symbols[i]->type == SY_ACCUM && i != 1)
+ error("Bad usage of 'accum' operand.");
+ }
+
+ if (nerr > 0)
+ return;
+
+ if (ninputs > 1) {
+ error("Attempt to access more than one input "
+ "GPRs by the same instruction");
+ }
+
+ for (i = 0; instructions[i].name != NULL; i++)
+ if (strcasecmp(tokens[0], instructions[i].name) == 0) {
+
+ if (is_audigy) {
+ EMIT_AUDIGY(instructions[i].opcode,
+ symbols[0]->arg,
+ symbols[1]->arg,
+ symbols[2]->arg,
+ symbols[3]->arg);
+ } else {
+ EMIT(instructions[i].opcode,
+ symbols[0]->arg,
+ symbols[1]->arg,
+ symbols[2]->arg,
+ symbols[3]->arg);
+ }
+
+ return;
+ }
+
+ error("Unrecognized instruction '%s'", tokens[0]);
+}
+
+static void
+init_compiler(void)
+{
+ char tmp[100];
+ int i;
+
+ (void) memset(&fle, 0, sizeof (fle));
+ /*
+ * Initialize few predefined GPR parameter registers. These
+ * definitions have to be in sync with the GPR_* macros in
+ * <sblive.h>.
+ */
+
+ /*
+ * Make sure we start at gpr id 2 for now; 0 and 1 may be used
+ * differently.
+ */
+ add_symbol("NULL", SY_DUMMY, gpr_base + ngpr++);
+ add_symbol("NULL_", SY_DUMMY, gpr_base + ngpr++);
+
+ pc = 0;
+
+ if (is_audigy) {
+ /* Initialize the code array with NOPs (AUDIGY) */
+ for (i = 0; i < 512; i++) {
+ fle.code[i * 2 + 0] = (0xc0 << 12) | 0xc0;
+ fle.code[i * 2 + 1] =
+ (0x06 << 24) | (0xc0 << 12) | 0xc0;
+ }
+
+ for (i = 0; i < 32; i++) {
+ (void) sprintf(tmp, "fx%d", i);
+ add_symbol(tmp, SY_FX, i);
+ }
+ } else {
+ /* Initialize the code array with NOPs (LIVE) */
+ for (i = 0; i < 512; i++) {
+ fle.code[i * 2 + 0] = 0x10040;
+ fle.code[i * 2 + 1] = 0x610040;
+ }
+
+ for (i = 0; i < 16; i++) {
+ (void) sprintf(tmp, "fx%d", i);
+ add_symbol(tmp, SY_FX, i);
+ }
+ }
+
+ /*
+ * Constants
+ */
+
+ if (is_audigy) {
+ /* Audigy symbols */
+ add_symbol("0", SY_CONST, 0x0c0);
+ add_symbol("1", SY_CONST, 0x0c1);
+ add_symbol("2", SY_CONST, 0x0c2);
+ add_symbol("3", SY_CONST, 0x0c3);
+ add_symbol("4", SY_CONST, 0x0c4);
+ add_symbol("8", SY_CONST, 0x0c5);
+ add_symbol("16", SY_CONST, 0x0c6);
+ add_symbol("32", SY_CONST, 0x0c7);
+ add_symbol("256", SY_CONST, 0x0c8);
+ add_symbol("65536", SY_CONST, 0x0c9);
+
+ add_symbol("2048", SY_CONST, 0x0ca);
+ add_symbol("0x800", SY_CONST, 0x0ca);
+
+ add_symbol("2^28", SY_CONST, 0x0cb);
+ add_symbol("0x10000000", SY_CONST, 0x0cb);
+
+ add_symbol("2^29", SY_CONST, 0x0cc);
+ add_symbol("0x20000000", SY_CONST, 0x0cc);
+
+ add_symbol("2^30", SY_CONST, 0x0cd);
+ add_symbol("0x40000000", SY_CONST, 0x0cd);
+
+ add_symbol("2^31", SY_CONST, 0x0ce);
+ add_symbol("0x80000000", SY_CONST, 0x0ce);
+
+ add_symbol("0x7fffffff", SY_CONST, 0x0cf);
+
+ add_symbol("0xffffffff", SY_CONST, 0x0d0);
+ add_symbol("-1", SY_CONST, 0x0d0);
+
+ add_symbol("0xfffffffe", SY_CONST, 0x0d1);
+ add_symbol("-2", SY_CONST, 0x0d1);
+
+ add_symbol("0xc0000000", SY_CONST, 0x0d2);
+
+ add_symbol("0x4f1bbcdc", SY_CONST, 0x0d3);
+
+ add_symbol("0x5a7ef9db", SY_CONST, 0x0d4);
+
+ add_symbol("0x100000", SY_CONST, 0x0d5);
+ add_symbol("accum", SY_ACCUM, 0x0d6);
+ add_symbol("CCR", SY_CONST, 0x0d7);
+
+ add_symbol("noise_L", SY_CONST, 0x0d8);
+ add_symbol("noise_R", SY_CONST, 0x0d9);
+ add_symbol("IRQREQ", SY_CONST, 0x0da);
+ } else {
+ /* SB Live symbols */
+ add_symbol("0", SY_CONST, 0x040);
+ add_symbol("1", SY_CONST, 0x041);
+ add_symbol("2", SY_CONST, 0x042);
+ add_symbol("3", SY_CONST, 0x043);
+ add_symbol("4", SY_CONST, 0x044);
+ add_symbol("8", SY_CONST, 0x045);
+ add_symbol("16", SY_CONST, 0x046);
+ add_symbol("32", SY_CONST, 0x047);
+ add_symbol("256", SY_CONST, 0x048);
+ add_symbol("65536", SY_CONST, 0x049);
+
+ add_symbol("2^23", SY_CONST, 0x04a);
+ add_symbol("0x80000", SY_CONST, 0x04a);
+
+ add_symbol("2^28", SY_CONST, 0x04b);
+ add_symbol("0x10000000", SY_CONST, 0x04b);
+
+ add_symbol("2^29", SY_CONST, 0x04c);
+ add_symbol("0x20000000", SY_CONST, 0x04c);
+
+ add_symbol("2^30", SY_CONST, 0x04d);
+ add_symbol("0x40000000", SY_CONST, 0x04d);
+
+ add_symbol("2^31", SY_CONST, 0x04e);
+ add_symbol("0x80000000", SY_CONST, 0x04e);
+
+ add_symbol("0x7fffffff", SY_CONST, 0x04f);
+
+ add_symbol("0xffffffff", SY_CONST, 0x050);
+ add_symbol("-1", SY_CONST, 0x050);
+
+ add_symbol("0xfffffffe", SY_CONST, 0x051);
+ add_symbol("-2", SY_CONST, 0x051);
+
+ add_symbol("accum", SY_ACCUM, 0x056);
+ add_symbol("CCR", SY_CONST, 0x057);
+
+ add_symbol("noise_L", SY_CONST, 0x058);
+ add_symbol("noise_R", SY_CONST, 0x059);
+ add_symbol("IRQREQ", SY_CONST, 0x05a);
+ }
+}
+
+static void
+produce_map(char *name)
+{
+ char fname[1024];
+ int i;
+ FILE *f;
+
+ if ((f = fopen(name, "w")) == NULL) {
+ perror(name);
+ return;
+ }
+
+ (void) fprintf(f, "%d\n", pc);
+
+ for (i = 0; i < nsyms; i++) {
+ (void) fprintf(f, "%04x %x %s\n",
+ symtab[i].arg, symtab[i].type, symtab[i].name);
+ }
+
+ (void) fclose(f);
+ if (verbose) {
+ (void) fprintf(stderr,
+ "No errors detected - Map written to %s\n", name);
+ }
+}
+
+static void
+produce_output(char *fname)
+{
+ int fd;
+
+ if ((fd = creat(fname, 0644)) == -1) {
+ perror(fname);
+ exit(-1);
+ }
+
+ if (write(fd, &fle, sizeof (fle)) != sizeof (fle)) {
+ perror(fname);
+ exit(-1);
+ }
+
+ if (verbose) {
+ (void) fprintf(stderr,
+ "No errors detected - Binary written to %s\n",
+ fname);
+ }
+
+ (void) close(fd);
+}
+
+static void
+produce_header(char *fname, char *prefix)
+{
+ FILE *f;
+ char *s;
+ char sname[MAXPATHLEN + 1];
+ char dname[MAXPATHLEN + 1];
+ int i;
+ clock_t now;
+ char when[128];
+
+ /* get basename */
+ if (prefix == NULL) {
+ s = strrchr(fname, '/');
+ s = (s == NULL) ? fname : s + 1;
+ } else {
+ s = prefix;
+ }
+ (void) strlcpy(sname, s, sizeof (sname));
+
+ /* strip off any extension */
+ s = strchr(sname, '.');
+ if (s != NULL) {
+ *s = 0;
+ }
+ if ((f = fopen(fname, "w")) == NULL) {
+ perror(fname);
+ return;
+ }
+
+ if (remarks[0] != 0) {
+ (void) fprintf(f, "/*\n%s */\n", remarks);
+ }
+ now = time(NULL);
+ strftime(when, sizeof (when), "%c", localtime(&now));
+ (void) fprintf(f, banner, progname, when);
+
+ (void) strlcpy(dname, prefix ? prefix : sname, sizeof (dname));
+ for (i = 0; dname[i]; i++) {
+ dname[i] = toupper(dname[i]);
+ if (!isalnum(dname[i])) {
+ dname[i] = '_';
+ }
+ }
+
+ for (i = 0; i < fle.parms.ngpr; i++) {
+ (void) fprintf(f, "#define\t%s_%s\t\t%d\n",
+ dname, fle.parms.gpr[i].name, fle.parms.gpr[i].num);
+ }
+
+ (void) fprintf(f, "\n");
+
+ if (parms_only)
+ goto done;
+
+ (void) fprintf(f, "uint32_t %s_code[] = {\n", sname);
+
+ for (i = 0; i < pc * 2; i++) {
+ if (i == 0) {
+ (void) fprintf(f, "\t0x%08xU", fle.code[i]);
+ } else if ((i % 4) == 0) {
+ (void) fprintf(f, ",\n\t0x%08xU", fle.code[i]);
+ } else {
+ (void) fprintf(f, ", 0x%08xU", fle.code[i]);
+ }
+ }
+ (void) fprintf(f, "\n};\n");
+
+ (void) fprintf(f, "uint32_t %s_ninit = %d;\n", sname, fle.ninit);
+ (void) fprintf(f, "uint32_t %s_init[] = {\n", sname);
+
+ for (i = 0; i < fle.ninit; i++) {
+ if (fle.init[i].name[0]) {
+ (void) fprintf(f, "\t%u, 0x%x%s,\t/* %s */\n",
+ fle.init[i].gpr, fle.init[i].value,
+ fle.init[i].value >= 0x80000000U ? "U" : "",
+ fle.init[i].name);
+ } else {
+ (void) fprintf(f, "\t%u, 0x%x%s,\n",
+ fle.init[i].gpr, fle.init[i].value,
+ fle.init[i].value >= 0x80000000U ? "U" : "");
+ }
+ }
+ (void) fprintf(f, "};\n");
+
+done:
+ (void) fclose(f);
+ if (verbose) {
+ (void) fprintf(stderr,
+ "No errors detected - Header written to %s\n",
+ fname);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ char line[4096], *p, *s, *outfile;
+ char *iline;
+ int i;
+ FILE *input;
+ char *tokens[10];
+ int tokcnt;
+ char *mapfile = NULL;
+ char *header = NULL;
+ char *prefix = NULL;
+
+ outfile = NULL;
+ infile = NULL;
+ input = NULL;
+ progname = argv[0];
+
+ while ((i = getopt(argc, argv, "m:h:o:i:P:021v")) != EOF) {
+ switch (i) {
+ case 'o':
+ outfile = optarg;
+ break;
+ case 'i':
+ infile = strdup(optarg);
+ break;
+ case 'm':
+ mapfile = optarg;
+ break;
+ case 'P':
+ prefix = optarg;
+ break;
+ case 'h':
+ header = optarg;
+ break;
+ case '0':
+ parms_only = 1;
+ break;
+ case '2':
+ is_audigy = 1;
+ break;
+ case '1':
+ is_audigy = 0;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ default:
+ (void) fprintf(stderr,
+ "usage: %s [-m <map>] [-h <header>] "
+ "[-o <binary>] [-i <source>] [-2|-1]",
+ progname);
+ exit(-1);
+ break;
+ }
+ }
+
+ if ((outfile == NULL) && (mapfile == NULL) && (header == NULL)) {
+ outfile = "dsp.bin";
+ }
+
+ if (infile) {
+ input = fopen(infile, "r");
+ if (input == NULL) {
+ perror(infile);
+ exit(-1);
+ }
+ } else {
+ infile = strdup("<stdin>");
+ input = stdin;
+ }
+
+ if (is_audigy) {
+ gpr_base = 0x400;
+ input_base = 0x40;
+ output_base = 0x60;
+ if (verbose)
+ (void) fprintf(stderr, "Compiling for SB Audigy\n");
+ } else {
+ if (verbose)
+ (void) fprintf(stderr, "Compiling for SB Live\n");
+ }
+
+ init_compiler();
+
+ while ((tokcnt = getline(input, tokens)) != -1) {
+ /* skip empty lines */
+ if (tokcnt == 0) {
+ continue;
+ }
+
+ if (strcmp(tokens[0], "#") == 0) {
+ int num;
+ if ((tokcnt >= 3) &&
+ (sscanf(tokens[1], "%d", &num) == 1)) {
+ lineno = num;
+ free(infile);
+ infile = strdup(tokens[2]);
+ /* we don't want to count the # directive */
+ lineno--;
+ }
+
+ /* unknown # directive? muddle on... */
+ continue;
+ }
+ if (*tokens[0] == '.') {
+ compile_directive(tokens, tokcnt);
+ } else {
+ compile_asm(tokens, tokcnt);
+ }
+ }
+
+ if (lineno < 1) {
+ error("Empty input");
+ }
+
+ if (errors == 0) {
+ if (verbose) {
+ (void) fprintf(stderr,
+ "%d instructions out of 512 assembled\n", pc);
+ }
+
+ if (outfile)
+ produce_output(outfile);
+ if (mapfile)
+ produce_map(mapfile);
+ if (header)
+ produce_header(header, prefix);
+ }
+
+ if (errors > 0) {
+ (void) fprintf(stderr, "%d errors - compile failed\n", errors);
+ exit(-1);
+ }
+
+ return (0);
+}
diff --git a/usr/src/uts/common/io/audio/drv/audioemu10k/dsp/emu10k.dsp b/usr/src/uts/common/io/audio/drv/audioemu10k/dsp/emu10k.dsp
new file mode 100644
index 0000000000..667dba0be6
--- /dev/null
+++ b/usr/src/uts/common/io/audio/drv/audioemu10k/dsp/emu10k.dsp
@@ -0,0 +1,73 @@
+//
+// Copyright 2009 Sun Microsystems, Inc. All rights reserved.
+// Use is subject to license terms.
+//
+// Copyright (C) 4Front Technologies 1996-2008.
+//
+// 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
+
+ // Master volume
+ .mono VOL_PCM 75
+
+ // Monitor volumes
+ .stereo MON_SPDIF1 0
+ .stereo MON_SPDIF2 0
+ .stereo MON_DIGCD 0
+ .stereo MON_AUX2 0
+ .stereo MON_LINE2 0
+ .stereo MON_AC97 0
+
+ // Output levels for various channels
+ .stereo VOL_FRONT 100
+ .stereo VOL_SURR 100
+ .stereo VOL_SIDE 100
+ .mono VOL_CEN 100
+ .mono VOL_LFE 100
+ .stereo VOL_HEADPH 100
+
+ // Recording volume
+ .stereo VOL_REC 100
+
+ // Recording source enables
+ .bool REC_SPDIF1 0
+ .bool REC_SPDIF2 0
+ .bool REC_DIGCD 0
+ .bool REC_AC97 1
+ .bool REC_AUX2 0
+ .bool REC_LINE2 0
+ .bool REC_PCM 0
+
+ // Sends
+ .send FX_FRONT_L 0
+ .send FX_FRONT_R 1
+ .send FX_SURR_L 2
+ .send FX_SURR_R 3
+ .send FX_CEN 4
+ .send FX_LFE 5
+ .send FX_SIDE_L 6
+ .send FX_SIDE_R 7
+ .send FX_SPDIF_L 20
+ .send FX_SPDIF_R 21
+
+#ifdef AUDIGY
+#include "emu10k2.mac"
+#else
+#include "emu10k1.mac"
+#endif
diff --git a/usr/src/uts/common/io/audio/drv/audioemu10k/dsp/emu10k1.mac b/usr/src/uts/common/io/audio/drv/audioemu10k/dsp/emu10k1.mac
new file mode 100644
index 0000000000..745d34bb52
--- /dev/null
+++ b/usr/src/uts/common/io/audio/drv/audioemu10k/dsp/emu10k1.mac
@@ -0,0 +1,154 @@
+//
+// Copyright 2009 Sun Microsystems, Inc. All rights reserved.
+// Use is subject to license terms.
+//
+// Copyright (C) 4Front Technologies 1996-2008.
+//
+// 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
+
+ // Constants for EMU 10k1 (SB Live)
+
+ // Inputs
+ .input IN_AC97_L 0
+ .input IN_AC97_R 1
+ .input IN_DIGCD_L 2
+ .input IN_DIGCD_R 3
+ .input IN_ZV_L 4
+ .input IN_ZV_R 5
+ .input IN_SPDIF1_L 6 // TOSLink
+ .input IN_SPDIF1_R 7
+ .input IN_LINE2_L 8 // LiveDrive (Line/Mic In 1)
+ .input IN_LINE2_R 9
+ .input IN_SPDIF2_L 10 // LiveDrive (Coax S/PDIF input)
+ .input IN_SPDIF2_R 11
+ .input IN_AUX2_L 12 // LiveDrive (Line/Mic 2)
+ .input IN_AUX2_R 13
+
+ // Outputs
+ .output OUT_FRONT_L 0 // via AC'97
+ .output OUT_FRONT_R 1 // via AC'97
+ .output OUT_SPDIF_L 2
+ .output OUT_SPDIF_R 3
+ .output OUT_DCENTER 4 // Digital Center channel
+ .output OUT_DLFE 5 // Digital LFE
+ .output OUT_HEADPH_L 6 // LiveDrive headphone out
+ .output OUT_HEADPH_R 7
+ .output OUT_SURR_L 8 // Rear output
+ .output OUT_SURR_R 9
+ .output OUT_ADC_L 10 // Send to the ADC recording channel
+ .output OUT_ADC_R 11
+ .output OUT_MICREC 12 // Send to the mic recording buffer
+ .output OUT_AC97SURR_L 13 // AC97 Surround L
+ .output OUT_AC97SURR_R 14 // AC97 Surround R
+
+ .output OUT_ACENTER 17 // Analog center channel
+ .output OUT_ALFE 18 // Analog LFE output
+
+ // Temporaries
+ .gpr PCM_FRONT_L
+ .gpr PCM_FRONT_R
+ .gpr PCM_SURR_L
+ .gpr PCM_SURR_R
+ .gpr PCM_CEN
+ .gpr PCM_LFE
+ .gpr PCM_REC_L
+ .gpr PCM_REC_R
+
+ // Code
+
+ // Load up the PCM inputs. We multiply each of them by 4, as
+ // otherwise they are too quiet.
+ MACINTS(PCM_FRONT_L, 0 FX_FRONT_L, 4)
+ MACINTS(PCM_FRONT_R, 0 FX_FRONT_R, 4)
+ MACINTS(PCM_SURR_L, 0, FX_SURR_L, 4)
+ MACINTS(PCM_SURR_R, 0, FX_SURR_R, 4)
+ MACINTS(PCM_CEN, 0, FX_CEN, 4)
+ MACINTS(PCM_LFE, 0, FX_LFE, 4)
+
+ // Apply PCM (wave) volume
+ MACS(PCM_FRONT_L, 0, PCM_FRONT_L, VOL_PCM)
+ MACS(PCM_FRONT_R, 0, PCM_FRONT_R, VOL_PCM)
+ MACS(PCM_SURR_L, 0, PCM_SURR_L, VOL_PCM)
+ MACS(PCM_SURR_R, 0, PCM_SURR_R, VOL_PCM)
+ MACS(PCM_CEN, 0, PCM_CEN, VOL_PCM)
+ MACS(PCM_LFE, 0, PCM_LFE, VOL_PCM)
+
+ // Mix any monitor sources into the front PCM
+ // AC'97 (includes Line-In, analog CD, and Mic)
+ MACS(PCM_FRONT_L, PCM_FRONT_L, IN_AC97_L, MON_AC97_L)
+ MACS(PCM_FRONT_R, PCM_FRONT_R, IN_AC97_R, MON_AC97_R)
+ // DIGCD
+ MACS(PCM_FRONT_L, PCM_FRONT_L, IN_DIGCD_L, MON_DIGCD_L)
+ MACS(PCM_FRONT_R, PCM_FRONT_R, IN_DIGCD_R, MON_DIGCD_R)
+ // SPDIF1
+ MACS(PCM_FRONT_L, PCM_FRONT_L, IN_SPDIF1_L, MON_SPDIF1_L)
+ MACS(PCM_FRONT_R, PCM_FRONT_R, IN_SPDIF1_R, MON_SPDIF1_R)
+ // SPDIF2
+ MACS(PCM_FRONT_L, PCM_FRONT_L, IN_SPDIF2_L, MON_SPDIF2_L)
+ MACS(PCM_FRONT_R, PCM_FRONT_R, IN_SPDIF2_R, MON_SPDIF2_R)
+ // Line2/Mic2 (Live! Drive)
+ MACS(PCM_FRONT_L, PCM_FRONT_L, IN_LINE2_L, MON_LINE2_L)
+ MACS(PCM_FRONT_R, PCM_FRONT_R, IN_LINE2_R, MON_LINE2_R)
+ // Aux2 (Live! Drive)
+ MACS(PCM_FRONT_L, PCM_FRONT_L, IN_AUX2_L, MON_AUX2_L)
+ MACS(PCM_FRONT_R, PCM_FRONT_R, IN_AUX2_R, MON_AUX2_R)
+
+ // Outputs
+ MACS(OUT_FRONT_L, 0, PCM_FRONT_L, VOL_FRONT_L)
+ MACS(OUT_FRONT_R, 0, PCM_FRONT_R, VOL_FRONT_R)
+ MACS(OUT_SPDIF_L, 0, PCM_FRONT_L, VOL_FRONT_L)
+ MACS(OUT_SPDIF_R, 0, PCM_FRONT_R, VOL_FRONT_R)
+ MACS(OUT_HEADPH_L, 0, PCM_FRONT_L, VOL_HEADPH_L)
+ MACS(OUT_HEADPH_R, 0, PCM_FRONT_R, VOL_HEADPH_R)
+ MACS(OUT_SURR_L, 0, PCM_SURR_L, VOL_SURR_L)
+ MACS(OUT_SURR_R, 0, PCM_SURR_R, VOL_SURR_R)
+ MACS(OUT_AC97SURR_L, 0, PCM_SURR_L, VOL_SURR_L)
+ MACS(OUT_AC97SURR_R, 0, PCM_SURR_R, VOL_SURR_R)
+ MACS(OUT_DCENTER, 0, PCM_CEN, VOL_CEN)
+ MACS(OUT_ACENTER, 0, PCM_CEN, VOL_CEN)
+ MACS(OUT_DLFE, 0, PCM_LFE, VOL_LFE)
+ MACS(OUT_ALFE, 0, PCM_LFE, VOL_LFE)
+
+ // Inputs (Recording) -- the source variables are treated as
+ // simple boolean enables.
+ MACINTS(PCM_REC_L, 0, IN_AC97_L, REC_AC97)
+ MACINTS(PCM_REC_R, 0, IN_AC97_R, REC_AC97)
+
+ MACINTS(PCM_REC_L, PCM_REC_L, IN_DIGCD_L, REC_DIGCD)
+ MACINTS(PCM_REC_R, PCM_REC_R, IN_DIGCD_R, REC_DIGCD)
+
+ MACINTS(PCM_REC_L, PCM_REC_L, IN_SPDIF1_L, REC_SPDIF1)
+ MACINTS(PCM_REC_R, PCM_REC_R, IN_SPDIF1_R, REC_SPDIF1)
+
+ MACINTS(PCM_REC_L, PCM_REC_L, IN_SPDIF2_L, REC_SPDIF2)
+ MACINTS(PCM_REC_R, PCM_REC_R, IN_SPDIF2_R, REC_SPDIF2)
+
+ MACINTS(PCM_REC_L, PCM_REC_L, IN_AUX2_L, REC_AUX2)
+ MACINTS(PCM_REC_R, PCM_REC_R, IN_AUX2_R, REC_AUX2)
+
+ MACINTS(PCM_REC_L, PCM_REC_L, IN_LINE2_L, REC_LINE2)
+ MACINTS(PCM_REC_R, PCM_REC_R, IN_LINE2_R, REC_LINE2)
+
+ MACINTS(PCM_REC_L, PCM_REC_L, PCM_FRONT_L, REC_PCM)
+ MACINTS(PCM_REC_R, PCM_REC_R, PCM_FRONT_R, REC_PCM)
+
+ // Apply master record gain
+ MACS(OUT_ADC_L, 0, PCM_REC_L, VOL_REC_L)
+ MACS(OUT_ADC_R, 0, PCM_REC_R, VOL_REC_R)
diff --git a/usr/src/uts/common/io/audio/drv/audioemu10k/dsp/emu10k2.mac b/usr/src/uts/common/io/audio/drv/audioemu10k/dsp/emu10k2.mac
new file mode 100644
index 0000000000..524b23bde0
--- /dev/null
+++ b/usr/src/uts/common/io/audio/drv/audioemu10k/dsp/emu10k2.mac
@@ -0,0 +1,159 @@
+//
+// Copyright 2009 Sun Microsystems, Inc. All rights reserved.
+// Use is subject to license terms.
+//
+// Copyright (C) 4Front Technologies 1996-2008.
+//
+// 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
+
+ // Constants for EMU 10k2 (SB Audigy)
+
+ // Inputs
+ .input IN_AC97_L 0
+ .input IN_AC97_R 1
+ .input IN_DIGCD_L 2
+ .input IN_DIGCD_R 3
+ .input IN_SPDIF1_L 4 // TOSLink
+ .input IN_SPDIF1_R 5
+ .input IN_MYSTERY_L 6
+ .input IN_MYSTERY_R 7
+ .input IN_LINE2_L 8 // LiveDrive
+ .input IN_LINE2_R 9
+ .input IN_SPDIF2_L 10 // Coaxial SPDIF
+ .input IN_SPDIF2_R 11
+ .input IN_AUX2_L 12 // LiveDrive
+ .input IN_AUX2_R 13
+
+ // Outputs
+ .output OUT_SPDIF_L 0
+ .output OUT_SPDIF_R 1
+ .output OUT_DCENTER 2 // Digital Center channel
+ .output OUT_DLFE 3 // Digital LFE
+ .output OUT_HEADPH_L 4 // LiveDrive headphone out
+ .output OUT_HEADPH_R 5
+ .output OUT_DSURR_L 6 // Surround output (digital)
+ .output OUT_DSURR_R 7
+ .output OUT_FRONT_L 8
+ .output OUT_FRONT_R 9
+ .output OUT_ACENTER 10 // Analog center channel
+ .output OUT_ALFE 11 // Analog LFE output
+ .output OUT_SIDE_L 12 // Side surround
+ .output OUT_SIDE_R 13
+ .output OUT_SURR_L 14 // Surround output
+ .output OUT_SURR_R 15
+ .output OUT_AC97_L 16 // Send to the AC97 front channel
+ .output OUT_AC97_R 17
+ .output OUT_ADC_L 22 // Send to the ADC recording channel
+ .output OUT_ADC_R 23
+ .output OUT_MICREC_L 24 // Send to the mic recording buffer
+ .output OUT_MICREC_R 25 // ??????? (maybe not in use at all)
+
+ // Temporaries
+ .gpr PCM_FRONT_L
+ .gpr PCM_FRONT_R
+ .gpr PCM_SURR_L
+ .gpr PCM_SURR_R
+ .gpr PCM_SIDE_L
+ .gpr PCM_SIDE_R
+ .gpr PCM_CEN
+ .gpr PCM_LFE
+ .gpr PCM_REC_L
+ .gpr PCM_REC_R
+ .gpr SPDIF_DELAY
+
+ // Code
+
+ // Load up the PCM inputs.
+ // We apply the PCM volume at the same time.
+ MACS(PCM_FRONT_L, 0, FX_FRONT_L, VOL_PCM)
+ MACS(PCM_FRONT_R, 0, FX_FRONT_R, VOL_PCM)
+ MACS(PCM_SURR_L, 0, FX_SURR_L, VOL_PCM)
+ MACS(PCM_SURR_R, 0, FX_SURR_R, VOL_PCM)
+ MACS(PCM_SIDE_L, 0, FX_SIDE_L, VOL_PCM)
+ MACS(PCM_SIDE_R, 0, FX_SIDE_R, VOL_PCM)
+ MACS(PCM_CEN, 0, FX_CEN, VOL_PCM)
+ MACS(PCM_LFE, 0, FX_LFE, VOL_PCM)
+
+ // Mix any monitor sources into the front PCM
+ // AC'97 (includes Line-In, analog CD, and Mic)
+ MACS(PCM_FRONT_L, PCM_FRONT_L, IN_AC97_L, MON_AC97_L)
+ MACS(PCM_FRONT_R, PCM_FRONT_R, IN_AC97_R, MON_AC97_R)
+ // DIGCD
+ MACS(PCM_FRONT_L, PCM_FRONT_L, IN_DIGCD_L, MON_DIGCD_L)
+ MACS(PCM_FRONT_R, PCM_FRONT_R, IN_DIGCD_R, MON_DIGCD_R)
+ // SPDIF1
+ MACS(PCM_FRONT_L, PCM_FRONT_L, IN_SPDIF1_L, MON_SPDIF1_L)
+ MACS(PCM_FRONT_R, PCM_FRONT_R, IN_SPDIF1_R, MON_SPDIF1_R)
+ // SPDIF2
+ MACS(PCM_FRONT_L, PCM_FRONT_L, IN_SPDIF2_L, MON_SPDIF2_L)
+ MACS(PCM_FRONT_R, PCM_FRONT_R, IN_SPDIF2_R, MON_SPDIF2_R)
+ // Line2/Mic2 (Live! Drive)
+ MACS(PCM_FRONT_L, PCM_FRONT_L, IN_LINE2_L, MON_LINE2_L)
+ MACS(PCM_FRONT_R, PCM_FRONT_R, IN_LINE2_R, MON_LINE2_R)
+ // Aux2 (Live! Drive)
+ MACS(PCM_FRONT_L, PCM_FRONT_L, IN_AUX2_L, MON_AUX2_L)
+ MACS(PCM_FRONT_R, PCM_FRONT_R, IN_AUX2_R, MON_AUX2_R)
+
+ // Outputs
+ MACS(OUT_FRONT_L, 0, PCM_FRONT_L, VOL_FRONT_L)
+ MACS(OUT_FRONT_R, 0, PCM_FRONT_R, VOL_FRONT_R)
+ MACS(OUT_SPDIF_L, 0, PCM_FRONT_L, VOL_FRONT_L)
+ // delay SPDIF right channel one sample to fix a bug
+ ACC3(OUT_SPDIF_R, 0, 0, SPDIF_DELAY)
+ MACS(SPDIF_DELAY, 0, PCM_FRONT_R, VOL_FRONT_R)
+ MACS(OUT_HEADPH_L, 0, PCM_FRONT_L, VOL_HEADPH_L)
+ MACS(OUT_HEADPH_R, 0, PCM_FRONT_R, VOL_HEADPH_R)
+ MACS(OUT_SURR_L, 0, PCM_SURR_L, VOL_SURR_L)
+ MACS(OUT_SURR_R, 0, PCM_SURR_R, VOL_SURR_R)
+ MACS(OUT_DSURR_L, 0, PCM_SURR_L, VOL_SURR_L)
+ MACS(OUT_DSURR_R, 0, PCM_SURR_R, VOL_SURR_R)
+ MACS(OUT_SIDE_L, 0, PCM_SIDE_L, VOL_SIDE_L)
+ MACS(OUT_SIDE_R, 0, PCM_SIDE_R, VOL_SIDE_R)
+ MACS(OUT_ACENTER, 0, PCM_CEN, VOL_CEN)
+ MACS(OUT_DCENTER, 0, PCM_CEN, VOL_CEN)
+ MACS(OUT_ALFE, 0, PCM_LFE, VOL_LFE)
+ MACS(OUT_DLFE, 0, PCM_LFE, VOL_LFE)
+
+ // Inputs (Recording) -- the source variables are treated as
+ // simple boolean enables.
+ MACINTS(PCM_REC_L, PCM_REC_R, IN_AC97_L, REC_AC97)
+ MACINTS(PCM_REC_R, PCM_REC_L, IN_AC97_R, REC_AC97)
+
+ MACINTS(PCM_REC_L, PCM_REC_L, IN_DIGCD_L, REC_DIGCD)
+ MACINTS(PCM_REC_R, PCM_REC_R, IN_DIGCD_R, REC_DIGCD)
+
+ MACINTS(PCM_REC_L, PCM_REC_L, IN_SPDIF1_L, REC_SPDIF1)
+ MACINTS(PCM_REC_R, PCM_REC_R, IN_SPDIF1_R, REC_SPDIF1)
+
+ MACINTS(PCM_REC_L, PCM_REC_L, IN_SPDIF2_L, REC_SPDIF2)
+ MACINTS(PCM_REC_R, PCM_REC_R, IN_SPDIF2_R, REC_SPDIF2)
+
+ MACINTS(PCM_REC_L, PCM_REC_L, IN_AUX2_L, REC_AUX2)
+ MACINTS(PCM_REC_R, PCM_REC_R, IN_AUX2_R, REC_AUX2)
+
+ MACINTS(PCM_REC_L, PCM_REC_L, IN_LINE2_L, REC_LINE2)
+ MACINTS(PCM_REC_R, PCM_REC_R, IN_LINE2_R, REC_LINE2)
+
+ MACINTS(PCM_REC_L, PCM_REC_L, PCM_FRONT_L, REC_PCM)
+ MACINTS(PCM_REC_R, PCM_REC_R, PCM_FRONT_R, REC_PCM)
+
+ // Apply master record gain
+ MACS(OUT_ADC_L, 0, PCM_REC_L, VOL_REC_L)
+ MACS(OUT_ADC_R, 0, PCM_REC_R, VOL_REC_R)