Mercurial > illumos > illumos-gate
changeset 10913:1d1ed05d0838
PSARC 2009/519 audioemu10k device driver
6539690 add sound driver for EMU10K chip
line wrap: on
line diff
--- a/usr/src/pkgdefs/Makefile Fri Oct 30 10:52:42 2009 +0800 +++ b/usr/src/pkgdefs/Makefile Thu Oct 29 21:38:34 2009 -0700 @@ -112,6 +112,7 @@ SUNWatge \ SUNWatu \ SUNWaudiocmi \ + SUNWaudioemu10k \ SUNWaudiohd \ SUNWaudiosolo \ SUNWaudiovia97 \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/pkgdefs/SUNWaudioemu10k/Makefile Thu Oct 29 21:38:34 2009 -0700 @@ -0,0 +1,36 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +include ../Makefile.com + +TMPLFILES += postinstall preremove + +.KEEP_STATE: + +all: $(FILES) depend +install: all pkg + +include ../Makefile.targ +include ../Makefile.prtarg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/pkgdefs/SUNWaudioemu10k/depend Thu Oct 29 21:38:34 2009 -0700 @@ -0,0 +1,50 @@ +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# This package information file defines software dependencies associated +# with the pkg. You can define three types of pkg dependencies with this file: +# P indicates a prerequisite for installation +# I indicates an incompatible package +# R indicates a reverse dependency +# <pkg.abbr> see pkginfo(4), PKG parameter +# <name> see pkginfo(4), NAME parameter +# <version> see pkginfo(4), VERSION parameter +# <arch> see pkginfo(4), ARCH parameter +# <type> <pkg.abbr> <name> +# (<arch>)<version> +# (<arch>)<version> +# ... +# <type> <pkg.abbr> <name> +# ... +# + +P SUNWcar Core Architecture, (Root) +P SUNWcakr Core Solaris Kernel Architecture (Root) +P SUNWkvm Core Architecture, (Kvm) +P SUNWcsr Core Solaris, (Root) +P SUNWckr Core Solaris Kernel (Root) +P SUNWcnetr Core Solaris Network Infrastructure (Root) +P SUNWcsu Core Solaris, (Usr) +P SUNWcsd Core Solaris Devices +P SUNWcsl Core Solaris Libraries +P SUNWaudd Audio Drivers
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/pkgdefs/SUNWaudioemu10k/pkginfo.tmpl Thu Oct 29 21:38:34 2009 -0700 @@ -0,0 +1,57 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# This required package information file describes characteristics of the +# package, such as package abbreviation, full package name, package version, +# and package architecture. +# +PKG="SUNWaudioemu10k" +NAME="Creative EMU10K Audio Driver" +ARCH="ISA" +VERSION="ONVERS,REV=0.0.0" +CATEGORY="system" +DESC="SunOS audio device driver for Creative EMU10K" +SUNW_PRODNAME="SunOS" +SUNW_PRODVERS="RELEASE/VERSION" +SUNW_PKGTYPE="root" +MAXINST="1000" +VENDOR="Sun Microsystems, Inc." +HOTLINE="Please contact your local service provider" +EMAIL="" +CLASSES="none preserve" +BASEDIR=/ +SUNW_PKGVERS="1.0" +SUNW_PKG_ALLZONES="true" +SUNW_PKG_HOLLOW="true" +SUNW_PKG_THISZONE="false" +#VSTOCK="<reserved by Release Engineering for package part #>" +#ISTATES="<developer defined>" +#RSTATES='<developer defined>' +#ULIMIT="<developer defined>" +#ORDER="<developer defined>" +#PSTAMP="<developer defined>" +#INTONLY="<developer defined>"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/pkgdefs/SUNWaudioemu10k/postinstall.tmpl Thu Oct 29 21:38:34 2009 -0700 @@ -0,0 +1,36 @@ +#! /bin/sh +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# SUNWaudioemu postinstall script + +include drv_utils + +DRVALIASES="\ + \"pci1102,2\" \ + \"pci1102,4\" \ + \"pci1102,8\" \ + " + +pkg_drvadd -i "${DRVALIASES}" audioemu10k || exit 1
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/pkgdefs/SUNWaudioemu10k/preremove.tmpl Thu Oct 29 21:38:34 2009 -0700 @@ -0,0 +1,30 @@ +#! /bin/sh +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# SUNWaudioemu10k preremove script + +include drv_utils + +pkg_drvrem audioemu10k || exit 1
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/pkgdefs/SUNWaudioemu10k/prototype_com Thu Oct 29 21:38:34 2009 -0700 @@ -0,0 +1,47 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search <pathname pathname ...> # where to find pkg objects +#!include <filename> # include another 'prototype' file +#!default <mode> <owner> <group> # default used if not specified on entry +#!<param>=<value> # puts parameter in pkg environment + +# packaging files +i pkginfo +i copyright +i depend +i postinstall +i preremove +# +# source locations relative to the prototype file +# +# SUNWaudioemu10k +# +d none kernel 755 root sys +d none kernel/drv 755 root sys
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/pkgdefs/SUNWaudioemu10k/prototype_i386 Thu Oct 29 21:38:34 2009 -0700 @@ -0,0 +1,48 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# This required package information file contains a list of package contents. +# The 'pkgmk' command uses this file to identify the contents of a package +# and their location on the development machine when building the package. +# Can be created via a text editor or through use of the 'pkgproto' command. + +#!search <pathname pathname ...> # where to find pkg objects +#!include <filename> # include another 'prototype' file +#!default <mode> <owner> <group> # default used if not specified on entry +#!<param>=<value> # puts parameter in pkg environment + +# +# Include ISA independent files (prototype_com) +# +!include prototype_com +# +# List files which are i386 specific here +# +# source locations relative to the prototype file +# +# SUNWaudioemu10k +# +f none kernel/drv/audioemu10k 755 root sys +d none kernel/drv/amd64 755 root sys +f none kernel/drv/amd64/audioemu10k 755 root sys
--- a/usr/src/uts/common/Makefile.files Fri Oct 30 10:52:42 2009 +0800 +++ b/usr/src/uts/common/Makefile.files Thu Oct 29 21:38:34 2009 -0700 @@ -433,6 +433,8 @@ audio_grc3.o audio_output.o audio_input.o \ audio_oss.o audio_sun.o +AUDIOEMU10K_OBJS += audioemu10k.o + AUDIOENS_OBJS += audioens.o AUDIOVIA823X_OBJS += audiovia823x.o
--- a/usr/src/uts/common/Makefile.rules Fri Oct 30 10:52:42 2009 +0800 +++ b/usr/src/uts/common/Makefile.rules Thu Oct 29 21:38:34 2009 -0700 @@ -580,6 +580,10 @@ $(COMPILE.c) -o $@ $< $(CTFCONVERT_O) +$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/audio/drv/audioemu10k/%.c + $(COMPILE.c) -o $@ $< + $(CTFCONVERT_O) + $(OBJS_DIR)/%.o: $(UTSBASE)/common/io/audio/drv/audio1575/%.c $(COMPILE.c) -o $@ $< $(CTFCONVERT_O) @@ -1896,6 +1900,9 @@ $(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/audio/drv/audioens/%.c @($(LHEAD) $(LINT.c) $< $(LTAIL)) +$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/audio/drv/audioemu10k/%.c + @($(LHEAD) $(LINT.c) $< $(LTAIL)) + $(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/audio/drv/audiohd/%.c @($(LHEAD) $(LINT.c) $< $(LTAIL))
--- a/usr/src/uts/common/io/audio/ac97/ac97.c Fri Oct 30 10:52:42 2009 +0800 +++ b/usr/src/uts/common/io/audio/ac97/ac97.c Thu Oct 29 21:38:34 2009 -0700 @@ -1217,12 +1217,6 @@ 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 @@ 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 @@ { 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 @@ { 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" },
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/common/io/audio/drv/audioemu10k/audioemu10k.c Thu Oct 29 21:38:34 2009 -0700 @@ -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); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/common/io/audio/drv/audioemu10k/audioemu10k.h Thu Oct 29 21:38:34 2009 -0700 @@ -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 */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/common/io/audio/drv/audioemu10k/dsp/asm10k.c Thu Oct 29 21:38:34 2009 -0700 @@ -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); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/common/io/audio/drv/audioemu10k/dsp/emu10k.dsp Thu Oct 29 21:38:34 2009 -0700 @@ -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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/common/io/audio/drv/audioemu10k/dsp/emu10k1.mac Thu Oct 29 21:38:34 2009 -0700 @@ -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)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/common/io/audio/drv/audioemu10k/dsp/emu10k2.mac Thu Oct 29 21:38:34 2009 -0700 @@ -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)
--- a/usr/src/uts/common/sys/audio/ac97.h Fri Oct 30 10:52:42 2009 +0800 +++ b/usr/src/uts/common/sys/audio/ac97.h Thu Oct 29 21:38:34 2009 -0700 @@ -515,9 +515,15 @@ #define AC97_VENDOR_EMC 0x454d4300 /* eMicro */ #define AC97_VENDOR_EV 0x000f8300 /* Ectiva */ #define AC97_VENDOR_ESS 0x45838300 /* ESS */ +#define AC97_VENDOR_HRS 0x48525300 /* Intersil */ #define AC97_VENDOR_ICE 0x49434500 /* ICEnsemble */ +#define AC97_VENDOR_ITE 0x49544500 /* ITE */ +#define AC97_VENDOR_NSC 0x4e534300 /* National */ +#define AC97_VENDOR_PSC 0x50534300 /* Philips */ +#define AC97_VENDOR_SIL 0x53494c00 /* Silicon Labs */ #define AC97_VENDOR_ST 0x83847600 /* SigmaTel */ #define AC97_VENDOR_TRA 0x54524100 /* TriTech */ +#define AC97_VENDOR_TXN 0x54584e00 /* TI */ #define AC97_VENDOR_VIA 0x56494100 /* VIA */ #define AC97_VENDOR_WML 0x574d4c00 /* Wolfson */ #define AC97_VENDOR_YMH 0x594d4800 /* Yamaha */ @@ -567,6 +573,7 @@ #define AC97_CODEC_ES1921 0x45838308 #define AC97_CODEC_EV1938 0x000f8384 #define AC97_CODEC_ICE1232 0x49434511 +#define AC97_CODEC_LM4550 0x4e534350 #define AC97_CODEC_STAC9700 0x83847600 #define AC97_CODEC_STAC9701 0x83847601 #define AC97_CODEC_STAC9701_2 0xc250c250
--- a/usr/src/uts/intel/Makefile.intel.shared Fri Oct 30 10:52:42 2009 +0800 +++ b/usr/src/uts/intel/Makefile.intel.shared Thu Oct 29 21:38:34 2009 -0700 @@ -200,6 +200,7 @@ DRV_KMODS += audio1575 DRV_KMODS += audio810 DRV_KMODS += audiocmi +DRV_KMODS += audioemu10k DRV_KMODS += audioens DRV_KMODS += audiohd DRV_KMODS += audioixp
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/intel/audioemu10k/Makefile Thu Oct 29 21:38:34 2009 -0700 @@ -0,0 +1,104 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# +# +# uts/intel/audioemu10k/Makefile +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# This makefile drives the production of the audioemu10k driver. +# + +# +# Path to the base of the uts directory tree (usually /usr/src/uts). +# +UTSBASE = ../.. + +# +# Define the module and object file sets. +# +MODULE = audioemu10k +OBJECTS = $(AUDIOEMU10K_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(AUDIOEMU10K_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE = $(ROOT_DRV_DIR)/$(MODULE) +DSP_SRCDIR = $(UTSBASE)/common/io/audio/drv/audioemu10k/dsp +DSP_HNAMES = emu10k_gpr.h emu10k1_dsp.h emu10k2_dsp.h +DSP_HDRS = $(DSP_HNAMES:%=$(OBJS_DIR)/%) +DSP_SNAMES = emu10k.dsp emu10k1.mac emu10k2.mac +DSP_SRCS = $(DSP_SNAMES:%=$(DSP_SRCDIR)/%) +ASM10K = $(OBJS_DIR)/asm10k + +# +# Include common rules. +# +include $(UTSBASE)/intel/Makefile.intel + +# +# Define targets +# +ALL_TARGET = $(BINARY) +LINT_TARGET = $(MODULE).lint +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) + +LDFLAGS += -dy -Ndrv/audio -Nmisc/ac97 + +$(OBJS_DIR)/%.o := CPPFLAGS += -I$(OBJS_DIR) +$(OBJS_DIR)/%.ln := CPPFLAGS += -I$(OBJS_DIR) +$(OBJS_DIR)/emu10k_gpr.h := ASM10KFLAGS = -v -0 -P gpr +$(OBJS_DIR)/emu10k1_dsp.h := ASM10KFLAGS = -v -1 -P emu10k1 +$(OBJS_DIR)/emu10k2_dsp.h := MODEL10K = SBLIVE +$(OBJS_DIR)/emu10k2_dsp.h := ASM10KFLAGS = -v -2 -P emu10k2 +$(OBJS_DIR)/emu10k2_dsp.h := MODEL10K = AUDIGY + +# +# Default build targets. +# +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +lint: $(LINT_DEPS) + +modlintlib: $(MODLINTLIB_DEPS) + +clean.lint: $(CLEAN_LINT_DEPS) + +install: $(INSTALL_DEPS) + +$(BINARY): $(OBJS_DIR)/asm10k $(DSP_HDRS) + +$(ASM10K): $(DSP_SRCDIR)/asm10k.c + $(CC) $(CFLAGS) -o $@ $(DSP_SRCDIR)/asm10k.c + +$(DSP_HDRS): $(ASM10K) $(DSP_SRCS) + $(CPP) -D$(MODEL10K) -I$(DSP_SRCDIR) $(DSP_SRCDIR)/emu10k.dsp | \ + $(OBJS_DIR)/asm10k $(ASM10KFLAGS) -h $@ + +# +# Include common targets. +# +include $(UTSBASE)/intel/Makefile.targ