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