Mercurial > illumos > illumos-gate
changeset 9831:60c34c50144c
PSARC 2009/263 audiocmi driver
6510438 sol x86 audio should support CMedia chipset
author | Garrett D'Amore <gdamore@opensolaris.org> |
---|---|
date | Tue, 09 Jun 2009 11:09:06 -0700 |
parents | eed00643bbdd |
children | 3569b6c7f56c |
files | usr/src/pkgdefs/Makefile usr/src/pkgdefs/SUNWaudiocmi/Makefile usr/src/pkgdefs/SUNWaudiocmi/depend usr/src/pkgdefs/SUNWaudiocmi/pkginfo.tmpl usr/src/pkgdefs/SUNWaudiocmi/postinstall.tmpl usr/src/pkgdefs/SUNWaudiocmi/preremove.tmpl usr/src/pkgdefs/SUNWaudiocmi/prototype_com usr/src/pkgdefs/SUNWaudiocmi/prototype_i386 usr/src/uts/common/Makefile.files usr/src/uts/common/Makefile.rules usr/src/uts/common/io/audio/drv/audiocmi/audiocmi.c usr/src/uts/common/io/audio/drv/audiocmi/audiocmi.h usr/src/uts/intel/Makefile.intel.shared usr/src/uts/intel/audiocmi/Makefile |
diffstat | 14 files changed, 1926 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/usr/src/pkgdefs/Makefile Tue Jun 09 07:24:43 2009 -0700 +++ b/usr/src/pkgdefs/Makefile Tue Jun 09 11:09:06 2009 -0700 @@ -118,6 +118,7 @@ SUNWatheros \ SUNWatigfx \ SUNWatu \ + SUNWaudiocmi \ SUNWaudiohd \ SUNWcakr.i \ SUNWcakrx.i \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/pkgdefs/SUNWaudiocmi/Makefile Tue Jun 09 11:09:06 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/SUNWaudiocmi/depend Tue Jun 09 11:09:06 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/SUNWaudiocmi/pkginfo.tmpl Tue Jun 09 11:09:06 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="SUNWaudiocmi" +NAME="C-Media 8738 Family Audio Driver" +ARCH="ISA" +VERSION="ONVERS,REV=0.0.0" +CATEGORY="system" +DESC="SunOS audio device driver for C-Media 8738" +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/SUNWaudiocmi/postinstall.tmpl Tue Jun 09 11:09:06 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. +# +# SUNWaudiocmi postinstall script + +include drv_utils + +AUDIOCMI_ALIASES="\ + \"pci13f6,111\" \ + \"pci13f6,100\" \ + \"pci13f6,101\" \ + " + +pkg_drvadd -i "${AUDIOCMI_ALIASES}" audiocmi || exit 1
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/pkgdefs/SUNWaudiocmi/preremove.tmpl Tue Jun 09 11:09:06 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. +# +# SUNWaudiocmi preremove script + +include drv_utils + +pkg_drvrem audiocmi || exit 1
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/pkgdefs/SUNWaudiocmi/prototype_com Tue Jun 09 11:09:06 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 +# +# SUNWaudiocmi +# +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/SUNWaudiocmi/prototype_i386 Tue Jun 09 11:09:06 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 +# +# SUNWaudiocmi +# +f none kernel/drv/audiocmi 755 root sys +d none kernel/drv/amd64 755 root sys +f none kernel/drv/amd64/audiocmi 755 root sys
--- a/usr/src/uts/common/Makefile.files Tue Jun 09 07:24:43 2009 -0700 +++ b/usr/src/uts/common/Makefile.files Tue Jun 09 11:09:06 2009 -0700 @@ -434,6 +434,8 @@ AUDIO810_OBJS += audio810.o +AUDIOCMI_OBJS += audiocmi.o + AUDIOHD_OBJS += audiohd.o AUDIOIXP_OBJS += audioixp.o
--- a/usr/src/uts/common/Makefile.rules Tue Jun 09 07:24:43 2009 -0700 +++ b/usr/src/uts/common/Makefile.rules Tue Jun 09 11:09:06 2009 -0700 @@ -556,6 +556,10 @@ $(COMPILE.c) -o $@ $< $(CTFCONVERT_O) +$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/audio/drv/audiocmi/%.c + $(COMPILE.c) -o $@ $< + $(CTFCONVERT_O) + $(OBJS_DIR)/%.o: $(UTSBASE)/common/io/audio/drv/audiohd/%.c $(COMPILE.c) -o $@ $< $(CTFCONVERT_O) @@ -1761,6 +1765,9 @@ $(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/audio/drv/audio810/%.c @($(LHEAD) $(LINT.c) $< $(LTAIL)) +$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/audio/drv/audiocmi/%.c + @($(LHEAD) $(LINT.c) $< $(LTAIL)) + $(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/audio/drv/audioens/%.c @($(LHEAD) $(LINT.c) $< $(LTAIL))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/common/io/audio/drv/audiocmi/audiocmi.c Tue Jun 09 11:09:06 2009 -0700 @@ -0,0 +1,1206 @@ +/* + * 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 CMEDIA CM8738 PCI audio controller. + */ +/* + * This file is part of Open Sound System + * + * Copyright (C) 4Front Technologies 1996-2008. + * + * This software is released under CDDL 1.0 source license. + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + */ + +#include <sys/audio/audio_driver.h> +#include <sys/note.h> +#include <sys/pci.h> +#include "audiocmi.h" + +/* + * Note: The original 4Front driver had support SPDIF, dual dac, and + * multichannel surround options. However, we haven't got any cards + * with these advanced features available on them for testing, so + * we're just going to support basic analog stereo for now. + * + * Adding back support for the advanced features would be an + * interesting project for someone with access to suitable hardware. + * + * Note that each variant (CMI 8338, 8738-033, -037, -055, and 8768) + * seems to have significant differences in some of the registers. + * While programming these parts for basic stereo is pretty much the + * same on all parts, doing anything more than that can be + * sigificantly different for each part. + */ + +static ddi_device_acc_attr_t acc_attr = { + DDI_DEVICE_ATTR_V0, + DDI_STRUCTURE_LE_ACC, + DDI_STRICTORDER_ACC +}; + +static ddi_device_acc_attr_t buf_attr = { + DDI_DEVICE_ATTR_V0, + DDI_NEVERSWAP_ACC, + DDI_STRICTORDER_ACC +}; + +static ddi_dma_attr_t dma_attr = { + DMA_ATTR_VERSION, /* dma_attr_version */ + 0x0, /* dma_attr_addr_lo */ + 0xffffffffU, /* dma_attr_addr_hi */ + 0x3ffff, /* dma_attr_count_max */ + 0x8, /* dma_attr_align */ + 0x7f, /* dma_attr_burstsizes */ + 0x1, /* dma_attr_minxfer */ + 0x3ffff, /* dma_attr_maxxfer */ + 0x3ffff, /* dma_attr_seg */ + 0x1, /* dma_attr_sgllen */ + 0x1, /* dma_attr_granular */ + 0 /* dma_attr_flags */ +}; + +static uint_t +cmpci_intr(caddr_t arg1, caddr_t arg2) +{ + cmpci_dev_t *dev = (void *)arg1; + + uint32_t intstat, intctrl, intclear; + void (*cb0)(audio_engine_t *) = NULL; + void (*cb1)(audio_engine_t *) = NULL; + uint_t rv; + + _NOTE(ARGUNUSED(arg2)); + + rv = DDI_INTR_UNCLAIMED; + + mutex_enter(&dev->mutex); + if (dev->suspended) { + mutex_exit(&dev->mutex); + return (rv); + } + + intclear = 0; + intstat = GET32(dev, REG_INTSTAT); + intctrl = GET32(dev, REG_INTCTRL); + if ((intstat & INTSTAT_CH0_INT) && (intctrl & INTCTRL_CH0_EN)) { + intclear |= INTCTRL_CH0_EN; + cb0 = dev->port[0].callb; + } + if ((intstat & INTSTAT_CH1_INT) && (intctrl & INTCTRL_CH1_EN)) { + intclear |= INTCTRL_CH1_EN; + cb1 = dev->port[1].callb; + } + + /* toggle the bits that we are going to handle */ + if (intclear) { + CLR32(dev, REG_INTCTRL, intclear); + SET32(dev, REG_INTCTRL, intclear); + rv = DDI_INTR_CLAIMED; + + KSINTR(dev)->intrs[KSTAT_INTR_HARD]++; + } + + mutex_exit(&dev->mutex); + + if (cb0) { + (*cb0)(dev->port[0].engine); + } + if (cb1) { + (*cb1)(dev->port[1].engine); + } + + return (rv); +} + +static void +cmpci_reset_port(cmpci_port_t *port) +{ + cmpci_dev_t *dev = port->dev; + + if (dev->suspended) + return; + + port->offset = 0; + + /* reset channel */ + SET32(dev, REG_FUNCTRL0, port->fc0_rst_bit); + drv_usecwait(10); + CLR32(dev, REG_FUNCTRL0, port->fc0_rst_bit); + drv_usecwait(10); + + /* Set 48k 16-bit stereo -- these are just with all bits set. */ + SET32(dev, REG_FUNCTRL1, port->fc1_rate_mask); + SET32(dev, REG_CHFORMAT, port->chformat_mask); + + PUT32(dev, port->reg_paddr, port->paddr); + PUT16(dev, port->reg_bufsz, port->nframes - 1); + PUT16(dev, port->reg_fragsz, port->fragfr - 1); + + /* Analog output */ + if (port->capture) { + /* Analog capture */ + SET32(dev, REG_FUNCTRL0, port->fc0_rec_bit); + } else { + CLR32(dev, REG_FUNCTRL0, port->fc0_rec_bit); + } +} + +static void +cmpci_start_port(cmpci_port_t *port) +{ + cmpci_dev_t *dev = port->dev; + + if (dev->suspended) + return; + + SET32(dev, REG_FUNCTRL0, port->fc0_en_bit); + SET32(dev, REG_INTCTRL, port->int_en_bit); +} + +static void +cmpci_stop_port(cmpci_port_t *port) +{ + cmpci_dev_t *dev = port->dev; + + if (dev->suspended) + return; + + CLR32(dev, REG_FUNCTRL0, port->fc0_en_bit); + CLR32(dev, REG_INTCTRL, port->int_en_bit); +} + +static int +cmpci_open(void *arg, int flag, uint_t *fragfrp, uint_t *nfp, caddr_t *bufp) +{ + cmpci_port_t *port = arg; + cmpci_dev_t *dev = port->dev; + int intrs; + + mutex_enter(&dev->mutex); + + if (flag & ENGINE_INPUT) { + intrs = dev->rintrs; + } else { + intrs = dev->pintrs; + } + + /* + * Calculate fragfr, nfrags, buf. + * + * 48 as minimum is chosen to ensure that we will have at + * least 4 fragments. 512 is just an arbitrary limit, and at + * the smallest frame size will result in no more than 176 + * fragments. + */ + intrs = min(512, max(48, intrs)); + + port->fragfr = (48000 / intrs); + port->nfrags = CMPCI_BUF_LEN / (port->fragfr * 4); + port->nframes = port->nfrags * port->fragfr; + port->count = 0; + port->bufsz = port->nframes * 4; + + *fragfrp = port->fragfr; + *nfp = port->nfrags; + *bufp = port->kaddr; + + port->open = B_TRUE; + + cmpci_reset_port(port); + cmpci_start_port(port); + + mutex_exit(&dev->mutex); + return (0); +} + +static void +cmpci_close(void *arg) +{ + cmpci_port_t *port = arg; + cmpci_dev_t *dev = port->dev; + + mutex_enter(&dev->mutex); + port->open = B_FALSE; + cmpci_stop_port(port); + mutex_exit(&dev->mutex); +} + +static void +cmpci_update_port(cmpci_port_t *port) +{ + cmpci_dev_t *dev = port->dev; + uint32_t count; + uint32_t offset; + + if ((dev->suspended) || (!port->open)) + return; + + offset = GET32(dev, port->reg_paddr) - port->paddr; + + /* check for wrap */ + if (offset < port->offset) { + count = (port->bufsz - port->offset) + offset; + } else { + count = offset - port->offset; + } + port->count += count; + port->offset = offset; +} + +static uint64_t +cmpci_count(void *arg) +{ + cmpci_port_t *port = arg; + cmpci_dev_t *dev = port->dev; + uint64_t count; + + mutex_enter(&dev->mutex); + cmpci_update_port(port); + + /* 4 is from 16-bit stereo */ + count = port->count / 4; + mutex_exit(&dev->mutex); + + /* NB: 2 because each sample is 2 bytes wide */ + return (count); +} + + +static int +cmpci_setup_interrupts(cmpci_dev_t *dev) +{ + int actual; + uint_t ipri; + + if ((ddi_intr_alloc(dev->dip, &dev->ihandle, DDI_INTR_TYPE_FIXED, + 0, 1, &actual, DDI_INTR_ALLOC_NORMAL) != DDI_SUCCESS) || + (actual != 1)) { + audio_dev_warn(dev->adev, "can't alloc intr handle"); + return (DDI_FAILURE); + } + + if (ddi_intr_get_pri(dev->ihandle, &ipri) != DDI_SUCCESS) { + audio_dev_warn(dev->adev, "can't determine intr priority"); + (void) ddi_intr_free(dev->ihandle); + dev->ihandle = NULL; + return (DDI_FAILURE); + } + + if (ddi_intr_add_handler(dev->ihandle, cmpci_intr, dev, + NULL) != DDI_SUCCESS) { + audio_dev_warn(dev->adev, "can't add intr handler"); + (void) ddi_intr_free(dev->ihandle); + dev->ihandle = NULL; + return (DDI_FAILURE); + } + + mutex_init(&dev->mutex, NULL, MUTEX_DRIVER, DDI_INTR_PRI(ipri)); + + return (DDI_SUCCESS); +} + + +#define MASK(nbits) ((1 << (nbits)) - 1) +#define SCALE(val, nbits) \ + ((uint8_t)((((val) * MASK(nbits)) / 100)) << (8 - (nbits))) + +#define LEFT(dev, ctl) min(((dev->controls[ctl].value) >> 8), 100) +#define RIGHT(dev, ctl) min(((dev->controls[ctl].value) & 0xff), 100) +#define MONO(dev, ctl) min(dev->controls[ctl].value, 100) + +static void +cmpci_setmixer(cmpci_dev_t *dev, uint8_t idx, uint8_t val) +{ + PUT8(dev, REG_IDXADDR, idx); + PUT8(dev, REG_IDXDATA, val); +} + +static uint8_t +cmpci_getmixer(cmpci_dev_t *dev, uint8_t idx) +{ + PUT8(dev, REG_IDXADDR, idx); + return (GET8(dev, REG_IDXDATA)); +} + + +static void +cmpci_configure_mixer(cmpci_dev_t *dev) +{ + uint64_t left, right; + uint8_t outmix; + uint8_t inmix[2]; + uint64_t recsrcs; + uint64_t monsrcs; + + if (dev->suspended) + return; + + /* reset all mix values */ + outmix = inmix[0] = inmix[1] = 0; + + outmix = OUTMIX_MIC | + OUTMIX_CD_R | OUTMIX_CD_L | OUTMIX_LINE_R | OUTMIX_LINE_L; + + inmix[0] = INMIX_LINE_L | INMIX_CD_L | INMIX_MIC; + inmix[1] = INMIX_LINE_R | INMIX_CD_R | INMIX_MIC; + + recsrcs = dev->controls[CTL_RECSRCS].value; + monsrcs = dev->controls[CTL_MONSRCS].value; + + /* program PCM volume */ + left = MONO(dev, CTL_VOLUME); + if (left) { + /* left and right are the same */ + cmpci_setmixer(dev, IDX_VOICE_LEFT, SCALE(left, 5)); + cmpci_setmixer(dev, IDX_VOICE_RIGHT, SCALE(left, 5)); + CLR8(dev, REG_MIX2, MIX2_WSMUTE); + } else { + cmpci_setmixer(dev, IDX_VOICE_LEFT, 0); + cmpci_setmixer(dev, IDX_VOICE_RIGHT, 0); + SET8(dev, REG_MIX2, MIX2_WSMUTE); + } + + left = LEFT(dev, CTL_LINEOUT); + right = RIGHT(dev, CTL_LINEOUT); + + /* lineout/master volume - no separate mute */ + cmpci_setmixer(dev, IDX_MASTER_LEFT, SCALE(left, 5)); + cmpci_setmixer(dev, IDX_MASTER_RIGHT, SCALE(right, 5)); + + /* speaker volume - mute in extension register, but we don't use */ + left = MONO(dev, CTL_SPEAKER); + cmpci_setmixer(dev, IDX_SPEAKER, SCALE(left, 2)); + + /* mic gain */ + left = MONO(dev, CTL_MIC); + if (left) { + cmpci_setmixer(dev, IDX_MIC, SCALE(left, 5)); + /* set record mic gain */ + uint8_t v = GET8(dev, REG_MIX3); + v &= ~(0x7 << 1); + v |= ((left * 7) / 100) << 1; + PUT8(dev, REG_MIX3, v); + cmpci_setmixer(dev, 0x3f, SCALE(100, 2)); + cmpci_setmixer(dev, 0x40, SCALE(100, 2)); + } else { + cmpci_setmixer(dev, IDX_MIC, 0); + outmix &= ~OUTMIX_MIC; + inmix[0] &= ~INMIX_MIC; + inmix[1] &= ~INMIX_MIC; + } + + /* line in */ + left = LEFT(dev, CTL_LINEOUT); + right = RIGHT(dev, CTL_LINEOUT); + if (left) { + cmpci_setmixer(dev, IDX_LINEIN_LEFT, SCALE(left, 5)); + } else { + cmpci_setmixer(dev, IDX_LINEIN_LEFT, 0); + inmix[0] &= ~INMIX_LINE_L; + outmix &= ~OUTMIX_LINE_L; + } + if (right) { + cmpci_setmixer(dev, IDX_LINEIN_RIGHT, SCALE(left, 5)); + } else { + cmpci_setmixer(dev, IDX_LINEIN_RIGHT, 0); + inmix[1] &= ~INMIX_LINE_R; + outmix &= ~OUTMIX_LINE_R; + } + + /* cd */ + left = LEFT(dev, CTL_CD); + right = RIGHT(dev, CTL_CD); + if (left) { + cmpci_setmixer(dev, IDX_CDDA_LEFT, SCALE(left, 5)); + } else { + cmpci_setmixer(dev, IDX_CDDA_LEFT, 0); + inmix[0] &= ~INMIX_CD_L; + outmix &= ~OUTMIX_CD_L; + } + if (right) { + cmpci_setmixer(dev, IDX_CDDA_RIGHT, SCALE(left, 5)); + } else { + cmpci_setmixer(dev, IDX_CDDA_RIGHT, 0); + inmix[1] &= ~INMIX_CD_R; + outmix &= ~OUTMIX_CD_R; + } + + /* aux - trickier because it doesn't use regular sbpro mixer */ + left = LEFT(dev, CTL_AUX); + right = RIGHT(dev, CTL_AUX); + PUT8(dev, REG_VAUX, (((left * 15) / 100) << 4) | ((right * 15) / 100)); + /* maybe enable recording */ + if ((left || right) && (recsrcs & (1 << SRC_LINE))) { + SET8(dev, REG_MIX3, MIX3_RAUXREN | MIX3_RAUXLEN); + } else { + CLR8(dev, REG_MIX3, MIX3_RAUXREN | MIX3_RAUXLEN); + } + /* maybe enable monitoring */ + if ((left || right) && (monsrcs & (1 << SRC_AUX))) { + CLR8(dev, REG_MIX3, MIX3_VAUXRM | MIX3_VAUXLM); + } else { + SET8(dev, REG_MIX3, MIX3_VAUXRM | MIX3_VAUXLM); + } + + /* now do the recsrcs */ + if ((recsrcs & (1 << SRC_MIC)) == 0) { + inmix[0] &= ~INMIX_MIC; + inmix[1] &= ~INMIX_MIC; + } + if ((recsrcs & (1 << SRC_LINE)) == 0) { + inmix[0] &= ~INMIX_LINE_L; + inmix[1] &= ~INMIX_LINE_R; + } + if ((recsrcs & (1 << SRC_CD)) == 0) { + inmix[0] &= ~INMIX_CD_L; + inmix[1] &= ~INMIX_CD_R; + } + if (recsrcs & (1 << SRC_MIX)) { + SET8(dev, REG_MIX2, MIX2_WAVEIN_L | MIX2_WAVEIN_R); + } else { + CLR8(dev, REG_MIX2, MIX2_WAVEIN_L | MIX2_WAVEIN_R); + } + cmpci_setmixer(dev, IDX_INMIX_L, inmix[0]); + cmpci_setmixer(dev, IDX_INMIX_R, inmix[1]); + + /* now the monsrcs */ + if ((monsrcs & (1 << SRC_MIC)) == 0) { + outmix &= ~OUTMIX_MIC; + } + if ((monsrcs & (1 << SRC_LINE)) == 0) { + outmix &= ~(OUTMIX_LINE_L | OUTMIX_LINE_R); + } + if ((monsrcs & (1 << SRC_CD)) == 0) { + outmix &= ~(OUTMIX_CD_L | OUTMIX_CD_R); + } + cmpci_setmixer(dev, IDX_OUTMIX, outmix); + + /* micboost */ + if (dev->controls[CTL_MICBOOST].value != 0) { + CLR8(dev, REG_MIX3, MIX3_MICGAINZ); + cmpci_setmixer(dev, IDX_EXTENSION, + cmpci_getmixer(dev, IDX_EXTENSION) & ~0x1); + } else { + SET8(dev, REG_MIX3, MIX3_MICGAINZ); + cmpci_setmixer(dev, IDX_EXTENSION, + cmpci_getmixer(dev, IDX_EXTENSION) | 0x1); + } +} + +static int +cmpci_set_ctrl(void *arg, uint64_t val) +{ + cmpci_ctrl_t *cc = arg; + cmpci_dev_t *dev = cc->dev; + + /* + * We don't bother to check for valid values - a bogus value + * will give incorrect volumes, but is otherwise harmless. + */ + mutex_enter(&dev->mutex); + cc->value = val; + cmpci_configure_mixer(dev); + mutex_exit(&dev->mutex); + + return (0); +} + +static int +cmpci_get_ctrl(void *arg, uint64_t *val) +{ + cmpci_ctrl_t *cc = arg; + cmpci_dev_t *dev = cc->dev; + + mutex_enter(&dev->mutex); + *val = cc->value; + mutex_exit(&dev->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 PCMVOL (PLAYCTL | AUDIO_CTRL_FLAG_PCMVOL) +#define MAINVOL (PLAYCTL | AUDIO_CTRL_FLAG_MAINVOL) +#define RECVOL (RECCTL | AUDIO_CTRL_FLAG_RECVOL) + +static void +cmpci_alloc_ctrl(cmpci_dev_t *dev, uint32_t num, uint64_t val) +{ + audio_ctrl_desc_t desc; + cmpci_ctrl_t *cc; + + cc = &dev->controls[num]; + bzero(&desc, sizeof (desc)); + cc->dev = dev; + + switch (num) { + case CTL_VOLUME: + desc.acd_name = AUDIO_CTRL_ID_VOLUME; + desc.acd_type = AUDIO_CTRL_TYPE_MONO; + desc.acd_minvalue = 0; + desc.acd_maxvalue = 100; + desc.acd_flags = PCMVOL; + break; + + case CTL_LINEOUT: + desc.acd_name = AUDIO_CTRL_ID_LINEOUT; + desc.acd_type = AUDIO_CTRL_TYPE_STEREO; + desc.acd_minvalue = 0; + desc.acd_maxvalue = 100; + desc.acd_flags = MAINVOL; + break; + + case CTL_SPEAKER: + desc.acd_name = AUDIO_CTRL_ID_SPEAKER; + desc.acd_type = AUDIO_CTRL_TYPE_MONO; + desc.acd_minvalue = 0; + desc.acd_maxvalue = 100; + desc.acd_flags = MAINVOL; + break; + + case CTL_MIC: + desc.acd_name = AUDIO_CTRL_ID_MIC; + desc.acd_type = AUDIO_CTRL_TYPE_MONO; + desc.acd_minvalue = 0; + desc.acd_maxvalue = 100; + desc.acd_flags = RECVOL; + break; + + case CTL_LINEIN: + desc.acd_name = AUDIO_CTRL_ID_LINEIN; + desc.acd_type = AUDIO_CTRL_TYPE_STEREO; + desc.acd_minvalue = 0; + desc.acd_maxvalue = 100; + desc.acd_flags = RECVOL; + break; + + case CTL_CD: + desc.acd_name = AUDIO_CTRL_ID_CD; + desc.acd_type = AUDIO_CTRL_TYPE_STEREO; + desc.acd_minvalue = 0; + desc.acd_maxvalue = 100; + desc.acd_flags = RECVOL; + break; + + case CTL_AUX: + desc.acd_name = AUDIO_CTRL_ID_AUX1IN; + desc.acd_type = AUDIO_CTRL_TYPE_STEREO; + desc.acd_minvalue = 0; + desc.acd_maxvalue = 100; + desc.acd_flags = RECVOL; + break; + + case CTL_RECSRCS: + desc.acd_name = AUDIO_CTRL_ID_RECSRC; + desc.acd_type = AUDIO_CTRL_TYPE_ENUM; + desc.acd_enum[SRC_MIC] = AUDIO_PORT_MIC; + desc.acd_enum[SRC_LINE] = AUDIO_PORT_LINEIN; + desc.acd_enum[SRC_CD] = AUDIO_PORT_CD; + desc.acd_enum[SRC_AUX] = AUDIO_PORT_AUX1IN; + desc.acd_enum[SRC_MIX] = AUDIO_PORT_STEREOMIX; + desc.acd_minvalue = (1 << (SRC_MIX + 1)) - 1; + desc.acd_maxvalue = desc.acd_minvalue; + desc.acd_flags = RECCTL | AUDIO_CTRL_FLAG_MULTI; + break; + + case CTL_MONSRCS: + desc.acd_name = AUDIO_CTRL_ID_MONSRC; + desc.acd_type = AUDIO_CTRL_TYPE_ENUM; + desc.acd_enum[SRC_MIC] = AUDIO_PORT_MIC; + desc.acd_enum[SRC_LINE] = AUDIO_PORT_LINEIN; + desc.acd_enum[SRC_CD] = AUDIO_PORT_CD; + desc.acd_enum[SRC_AUX] = AUDIO_PORT_AUX1IN; + desc.acd_minvalue = ((1 << (SRC_AUX + 1)) - 1); + desc.acd_maxvalue = desc.acd_minvalue; + desc.acd_flags = MONCTL | AUDIO_CTRL_FLAG_MULTI; + break; + + case CTL_MICBOOST: + desc.acd_name = AUDIO_CTRL_ID_MICBOOST; + desc.acd_type = AUDIO_CTRL_TYPE_BOOLEAN; + desc.acd_minvalue = 0; + desc.acd_maxvalue = 1; + desc.acd_flags = RECCTL; + break; + } + + cc->value = val; + cc->ctrl = audio_dev_add_control(dev->adev, &desc, + cmpci_get_ctrl, cmpci_set_ctrl, cc); +} + +static void +cmpci_add_controls(cmpci_dev_t *dev) +{ + cmpci_alloc_ctrl(dev, CTL_VOLUME, 75); + cmpci_alloc_ctrl(dev, CTL_LINEOUT, 90 | (90 << 8)); + cmpci_alloc_ctrl(dev, CTL_SPEAKER, 75); + cmpci_alloc_ctrl(dev, CTL_MIC, 32); + cmpci_alloc_ctrl(dev, CTL_LINEIN, 64 | (64 << 8)); + cmpci_alloc_ctrl(dev, CTL_CD, 75 | (75 << 8)); + cmpci_alloc_ctrl(dev, CTL_AUX, 75 | (75 << 8)); + cmpci_alloc_ctrl(dev, CTL_RECSRCS, (1 << SRC_MIC)); + cmpci_alloc_ctrl(dev, CTL_MONSRCS, 0); + cmpci_alloc_ctrl(dev, CTL_MICBOOST, 0); +} + +static void +cmpci_del_controls(cmpci_dev_t *dev) +{ + for (int i = 0; i < CTL_NUM; i++) { + if (dev->controls[i].ctrl) { + audio_dev_del_control(dev->controls[i].ctrl); + dev->controls[i].ctrl = NULL; + } + } +} + +static void +cmpci_reset(cmpci_dev_t *dev) +{ + /* Full reset */ + SET32(dev, REG_MISC, MISC_RESET); + (void) GET32(dev, REG_MISC); + drv_usecwait(100); + CLR32(dev, REG_MISC, MISC_RESET); + + /* reset all channels */ + PUT32(dev, REG_FUNCTRL0, 0); + + /* disable interrupts and such */ + CLR32(dev, REG_FUNCTRL0, FUNCTRL0_CH0_EN | FUNCTRL0_CH1_EN); + CLR32(dev, REG_INTCTRL, INTCTRL_CH0_EN | INTCTRL_CH1_EN); + + /* disable uart, joystick in Function Control Reg1 */ + CLR32(dev, REG_FUNCTRL1, FUNCTRL1_UART_EN | FUNCTRL1_JYSTK_EN); + + /* + * Set DAC and ADC rates to 48 kHz - note that both rates have + * all bits set in them, so we can do this with a simple "set". + */ + SET32(dev, REG_FUNCTRL1, + FUNCTRL1_DAC_RATE_48K | FUNCTRL1_ADC_RATE_48K); + + /* Set 16-bit stereo -- also these are just with all bits set. */ + SET32(dev, REG_CHFORMAT, CHFORMAT_CH0_16ST | CHFORMAT_CH1_16ST); +} + +static int +cmpci_format(void *unused) +{ + _NOTE(ARGUNUSED(unused)); + return (AUDIO_FORMAT_S16_LE); +} + +static int +cmpci_channels(void *unused) +{ + _NOTE(ARGUNUSED(unused)); + return (2); +} + +static int +cmpci_rate(void *unused) +{ + _NOTE(ARGUNUSED(unused)); + return (48000); +} + +static void +cmpci_sync(void *arg, unsigned nframes) +{ + cmpci_port_t *port = arg; + + _NOTE(ARGUNUSED(nframes)); + + (void) ddi_dma_sync(port->dmah, 0, 0, port->sync_dir); +} + +static size_t +cmpci_qlen(void *unused) +{ + _NOTE(ARGUNUSED(unused)); + + return (0); +} + +audio_engine_ops_t cmpci_engine_ops = { + AUDIO_ENGINE_VERSION, /* version number */ + cmpci_open, + cmpci_close, + NULL, /* start */ + NULL, /* stop */ + cmpci_count, + cmpci_format, + cmpci_channels, + cmpci_rate, + cmpci_sync, + cmpci_qlen, + NULL, +}; + +static int +cmpci_init(cmpci_dev_t *dev) +{ + audio_dev_t *adev = dev->adev; + + dev->pintrs = ddi_prop_get_int(DDI_DEV_T_ANY, dev->dip, + DDI_PROP_DONTPASS, "play-interrupts", DEFINTS); + + dev->rintrs = ddi_prop_get_int(DDI_DEV_T_ANY, dev->dip, + DDI_PROP_DONTPASS, "record-interrupts", DEFINTS); + + for (int i = 0; i < PORT_MAX; i++) { + + cmpci_port_t *port; + unsigned dmaflags; + unsigned caps; + size_t rlen; + ddi_dma_cookie_t c; + unsigned ccnt; + + port = &dev->port[i]; + port->dev = dev; + port->num = i; + + /* + * Channel 0 is recording channel, unless we are in + * dual DAC mode. The reason for this is simple -- + * only channel "B" (which I presume to mean channel + * 1) supports multichannel configuration. + * + * However, if we're going to use SPDIF recording, + * then recording *must* occur on channel 1. Yes, the + * hardware is "strange". + */ + + switch (i) { + case 0: + caps = ENGINE_INPUT_CAP; + dmaflags = DDI_DMA_READ | DDI_DMA_CONSISTENT; + port->callb = audio_engine_produce; + port->reg_paddr = REG_CH0_PADDR; + port->reg_bufsz = REG_CH0_BUFSZ; + port->reg_fragsz = REG_CH0_FRAGSZ; + port->fc0_rst_bit = FUNCTRL0_CH0_RST; + port->fc0_rec_bit = FUNCTRL0_CH0_REC; + port->fc0_en_bit = FUNCTRL0_CH0_EN; + port->int_en_bit = INTCTRL_CH0_EN; + port->sync_dir = DDI_DMA_SYNC_FORKERNEL; + port->capture = B_TRUE; + port->fc1_rate_mask = FUNCTRL1_ADC_RATE_48K; + port->chformat_mask = CHFORMAT_CH0_16ST; + break; + + case 1: + caps = ENGINE_OUTPUT_CAP; + dmaflags = DDI_DMA_WRITE | DDI_DMA_CONSISTENT; + port->callb = audio_engine_consume; + port->reg_paddr = REG_CH1_PADDR; + port->reg_bufsz = REG_CH1_BUFSZ; + port->reg_fragsz = REG_CH1_FRAGSZ; + port->fc0_rst_bit = FUNCTRL0_CH1_RST; + port->fc0_rec_bit = FUNCTRL0_CH1_REC; + port->fc0_en_bit = FUNCTRL0_CH1_EN; + port->int_en_bit = INTCTRL_CH1_EN; + port->sync_dir = DDI_DMA_SYNC_FORDEV; + port->capture = B_FALSE; + port->fc1_rate_mask = FUNCTRL1_DAC_RATE_48K; + port->chformat_mask = CHFORMAT_CH1_16ST; + break; + } + + if (ddi_dma_alloc_handle(dev->dip, &dma_attr, DDI_DMA_DONTWAIT, + NULL, &port->dmah) != DDI_SUCCESS) { + audio_dev_warn(adev, "ch%d: dma hdl alloc failed", i); + return (DDI_FAILURE); + } + if (ddi_dma_mem_alloc(port->dmah, CMPCI_BUF_LEN, &buf_attr, + DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, NULL, &port->kaddr, + &rlen, &port->acch) != DDI_SUCCESS) { + audio_dev_warn(adev, "ch%d: dma mem allcoc failed", i); + return (DDI_FAILURE); + } + bzero(port->kaddr, rlen); + + if (ddi_dma_addr_bind_handle(port->dmah, NULL, port->kaddr, + rlen, dmaflags, DDI_DMA_DONTWAIT, NULL, &c, &ccnt) != + DDI_DMA_MAPPED) { + audio_dev_warn(adev, "ch%d: dma bind failed", i); + return (DDI_FAILURE); + } + port->paddr = c.dmac_address; + + port->engine = audio_engine_alloc(&cmpci_engine_ops, caps); + if (port->engine == NULL) { + audio_dev_warn(adev, "ch%d: alloc engine failed", i); + return (DDI_FAILURE); + } + audio_engine_set_private(port->engine, port); + audio_dev_add_engine(adev, port->engine); + } + + cmpci_add_controls(dev); + + dev->ksp = kstat_create(ddi_driver_name(dev->dip), + ddi_get_instance(dev->dip), ddi_driver_name(dev->dip), + "controller", KSTAT_TYPE_INTR, 1, KSTAT_FLAG_PERSISTENT); + if (dev->ksp != NULL) { + kstat_install(dev->ksp); + } + + cmpci_reset(dev); + cmpci_configure_mixer(dev); + + if (audio_dev_register(adev) != DDI_SUCCESS) { + audio_dev_warn(adev, "audio_dev_register failed"); + return (DDI_FAILURE); + } + + return (DDI_SUCCESS); +} + +void +cmpci_destroy(cmpci_dev_t *dev) +{ + if (dev->ihandle != NULL) { + (void) ddi_intr_disable(dev->ihandle); + (void) ddi_intr_remove_handler(dev->ihandle); + (void) ddi_intr_free(dev->ihandle); + mutex_destroy(&dev->mutex); + } + + if (dev->ksp != NULL) { + kstat_delete(dev->ksp); + } + + /* free up ports, including DMA resources for ports */ + for (int i = 0; i < PORT_MAX; i++) { + cmpci_port_t *port = &dev->port[i]; + + if (port->paddr != 0) + (void) ddi_dma_unbind_handle(port->dmah); + if (port->acch != NULL) + ddi_dma_mem_free(&port->acch); + if (port->dmah != NULL) + ddi_dma_free_handle(&port->dmah); + + if (port->engine != NULL) { + audio_dev_remove_engine(dev->adev, port->engine); + audio_engine_free(port->engine); + } + } + + if (dev->acch != NULL) { + ddi_regs_map_free(&dev->acch); + } + + cmpci_del_controls(dev); + + if (dev->adev != NULL) { + audio_dev_free(dev->adev); + } + + kmem_free(dev, sizeof (*dev)); +} + +int +cmpci_attach(dev_info_t *dip) +{ + uint16_t vendor, device; + cmpci_dev_t *dev; + ddi_acc_handle_t pcih; + audio_dev_t *adev; + uint32_t val; + + if (pci_config_setup(dip, &pcih) != DDI_SUCCESS) { + audio_dev_warn(NULL, "pci_config_setup failed"); + return (DDI_FAILURE); + } + + vendor = pci_config_get16(pcih, PCI_CONF_VENID); + device = pci_config_get16(pcih, PCI_CONF_DEVID); + + if (vendor != CMEDIA_VENDOR_ID || + ((device != CMEDIA_CM8738) && (device != CMEDIA_CM8338A) && + (device != CMEDIA_CM8338B))) { + pci_config_teardown(&pcih); + audio_dev_warn(NULL, "device not recognized"); + return (DDI_FAILURE); + } + + /* enable IO and Master accesses */ + pci_config_put16(pcih, PCI_CONF_COMM, + pci_config_get16(pcih, PCI_CONF_COMM) | + PCI_COMM_MAE | PCI_COMM_IO); + + pci_config_teardown(&pcih); + + dev = kmem_zalloc(sizeof (*dev), KM_SLEEP); + dev->dip = dip; + + ddi_set_driver_private(dip, dev); + + if ((adev = audio_dev_alloc(dip, 0)) == NULL) { + goto err_exit; + } + dev->adev = adev; + + if (ddi_regs_map_setup(dip, 1, &dev->regs, 0, 0, &acc_attr, + &dev->acch) != DDI_SUCCESS) { + audio_dev_warn(adev, "can't map registers"); + goto err_exit; + } + + /* setup some initial values */ + audio_dev_set_description(adev, "C-Media PCI Audio"); + switch (device) { + case CMEDIA_CM8738: + /* Crazy 8738 detection scheme */ + val = GET32(dev, REG_INTCTRL) & 0x1F000000; + if (val == 0) { + + if (GET32(dev, REG_CHFORMAT & 0x1F000000)) { + audio_dev_set_version(adev, "CM8738-037"); + } else { + audio_dev_set_version(adev, "CM8738-033"); + } + } else if (val & 0x0C000000) { + audio_dev_set_version(adev, "CMI8768"); + } else if (val & 0x08000000) { + audio_dev_set_version(adev, "CMI8738-055"); + } else if (val & 0x04000000) { + audio_dev_set_version(adev, "CMI8738-039"); + } else { + audio_dev_set_version(adev, "CMI8738"); + } + break; + + case CMEDIA_CM8338A: + audio_dev_set_version(dev->adev, "CM8338A"); + break; + + case CMEDIA_CM8338B: + audio_dev_set_version(dev->adev, "CM8338B"); + break; + } + + if (cmpci_setup_interrupts(dev) != DDI_SUCCESS) { + audio_dev_warn(dev->adev, "can't register interrupts"); + goto err_exit; + } + + if (cmpci_init(dev) != DDI_SUCCESS) { + audio_dev_warn(dev->adev, "can't init device"); + goto err_exit; + } + + (void) ddi_intr_enable(dev->ihandle); + return (DDI_SUCCESS); + +err_exit: + cmpci_destroy(dev); + return (DDI_FAILURE); +} + +static int +cmpci_resume(cmpci_dev_t *dev) +{ + audio_engine_reset(dev->port[0].engine); + audio_engine_reset(dev->port[1].engine); + + mutex_enter(&dev->mutex); + dev->suspended = B_FALSE; + + cmpci_reset(dev); + /* wait one millisecond, to give reset a chance to get up */ + drv_usecwait(1000); + + cmpci_configure_mixer(dev); + + for (int i = 0; i < PORT_MAX; i++) { + cmpci_port_t *port = &dev->port[i]; + + cmpci_reset_port(port); + if (port->open) { + cmpci_start_port(port); + } + } + mutex_exit(&dev->mutex); + return (DDI_SUCCESS); +} + +static int +cmpci_detach(cmpci_dev_t *dev) +{ + if (audio_dev_unregister(dev->adev) != DDI_SUCCESS) + return (DDI_FAILURE); + + mutex_enter(&dev->mutex); + + /* disable interrupts */ + CLR32(dev, REG_INTCTRL, INTCTRL_CH1_EN | INTCTRL_CH0_EN); + + /* disable channels */ + PUT32(dev, REG_FUNCTRL0, 0); + + mutex_exit(&dev->mutex); + + cmpci_destroy(dev); + + return (DDI_SUCCESS); +} + +static int +cmpci_suspend(cmpci_dev_t *dev) +{ + mutex_enter(&dev->mutex); + + cmpci_update_port(&dev->port[0]); + cmpci_stop_port(&dev->port[0]); + + cmpci_update_port(&dev->port[1]); + cmpci_stop_port(&dev->port[1]); + + dev->suspended = B_TRUE; + mutex_exit(&dev->mutex); + + return (DDI_SUCCESS); +} + +static int +cmpci_quiesce(dev_info_t *dip) +{ + cmpci_dev_t *dev; + + if ((dev = ddi_get_driver_private(dip)) == NULL) { + return (DDI_FAILURE); + } + + /* disable interrupts */ + PUT32(dev, REG_INTCTRL, 0); + + /* disable channels */ + PUT32(dev, REG_FUNCTRL0, 0); + + return (DDI_SUCCESS); +} + +static int +cmpci_ddi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) +{ + cmpci_dev_t *dev; + + switch (cmd) { + case DDI_ATTACH: + return (cmpci_attach(dip)); + + case DDI_RESUME: + if ((dev = ddi_get_driver_private(dip)) == NULL) { + return (DDI_FAILURE); + } + return (cmpci_resume(dev)); + + default: + return (DDI_FAILURE); + } +} + +static int +cmpci_ddi_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) +{ + cmpci_dev_t *dev; + + if ((dev = ddi_get_driver_private(dip)) == NULL) { + return (DDI_FAILURE); + } + + switch (cmd) { + case DDI_DETACH: + return (cmpci_detach(dev)); + + case DDI_SUSPEND: + return (cmpci_suspend(dev)); + default: + return (DDI_FAILURE); + } +} + +static struct dev_ops cmpci_dev_ops = { + DEVO_REV, /* rev */ + 0, /* refcnt */ + NULL, /* getinfo */ + nulldev, /* identify */ + nulldev, /* probe */ + cmpci_ddi_attach, /* attach */ + cmpci_ddi_detach, /* detach */ + nodev, /* reset */ + NULL, /* cb_ops */ + NULL, /* bus_ops */ + NULL, /* power */ + cmpci_quiesce, /* quiesce */ +}; + +static struct modldrv cmpci_modldrv = { + &mod_driverops, /* drv_modops */ + "C-Media PCI Audio", /* linkinfo */ + &cmpci_dev_ops, /* dev_ops */ +}; + +static struct modlinkage modlinkage = { + MODREV_1, + { &cmpci_modldrv, NULL } +}; + +int +_init(void) +{ + int rv; + + audio_init_ops(&cmpci_dev_ops, "audiocmi"); + if ((rv = mod_install(&modlinkage)) != 0) { + audio_fini_ops(&cmpci_dev_ops); + } + return (rv); +} + +int +_fini(void) +{ + int rv; + if ((rv = mod_remove(&modlinkage)) == 0) { + audio_fini_ops(&cmpci_dev_ops); + } + return (rv); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/common/io/audio/drv/audiocmi/audiocmi.h Tue Jun 09 11:09:06 2009 -0700 @@ -0,0 +1,324 @@ +/* + * 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 CMEDIA CM8738 PCI audio controller. + */ +/* + * This file is part of Open Sound System + * + * Copyright (C) 4Front Technologies 1996-2008. + * + * 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 _AUDIOCMI_H +#define _AUDIOCMI_H + +#define CMEDIA_VENDOR_ID 0x13F6 +#define CMEDIA_CM8738 0x0111 +#define CMEDIA_CM8338A 0x0100 +#define CMEDIA_CM8338B 0x0101 + +/* + * CM8338 registers definition + */ + +#define REG_FUNCTRL0 0x00 +#define REG_FUNCTRL1 0x04 +#define REG_CHFORMAT 0x08 +#define REG_INTCTRL 0x0C +#define REG_INTSTAT 0x10 +#define REG_LEGACY 0x14 +#define REG_MISC 0x18 +#define REG_TDMAPOS 0x1C +#define REG_SBVER 0x20 /* 8 bit access only */ +#define REG_IDXDATA 0x22 /* 8 bit access only */ +#define REG_IDXADDR 0x23 /* 8 bit access only */ +#define REG_MIX2 0x24 +#define REG_MIX3 0x25 +#define REG_VAUX 0x26 +#define REG_CH0_PADDR 0x80 /* buffer address (32b) */ +#define REG_CH0_BUFSZ 0x84 /* buffer size in samples (16b) */ +#define REG_CH0_FRAGSZ 0x86 /* fragment size in samples (16b) */ +#define REG_CH1_PADDR 0x88 +#define REG_CH1_BUFSZ 0x8C +#define REG_CH1_FRAGSZ 0x8E + +#define FUNCTRL0_CH1_RST BIT(19) +#define FUNCTRL0_CH0_RST BIT(18) +#define FUNCTRL0_CH1_EN BIT(17) +#define FUNCTRL0_CH0_EN BIT(16) +#define FUNCTRL0_CH1_PAUSE BIT(3) +#define FUNCTRL0_CH0_PAUSE BIT(2) +#define FUNCTRL0_CH1_REC BIT(1) +#define FUNCTRL0_CH0_REC BIT(0) + +#define FUNCTRL1_DAC_RATE_MASK (0x7 << 13) +#define FUNCTRL1_DAC_RATE_48K (0x7 << 13) +#define FUNCTRL1_DAC_RATE_32K (0x6 << 13) +#define FUNCTRL1_DAC_RATE_16K (0x5 << 13) +#define FUNCTRL1_DAC_RATE_8K (0x4 << 13) +#define FUNCTRL1_DAC_RATE_44K (0x3 << 13) +#define FUNCTRL1_DAC_RATE_22K (0x2 << 13) +#define FUNCTRL1_DAC_RATE_11K (0x1 << 13) +#define FUNCTRL1_DAC_RATE_5K (0x0 << 13) +#define FUNCTRL1_ADC_RATE_MASK (0x7 << 10) +#define FUNCTRL1_ADC_RATE_48K (0x7 << 10) +#define FUNCTRL1_ADC_RATE_32K (0x6 << 10) +#define FUNCTRL1_ADC_RATE_16K (0x5 << 10) +#define FUNCTRL1_ADC_RATE_8K (0x4 << 10) +#define FUNCTRL1_ADC_RATE_44K (0x3 << 10) +#define FUNCTRL1_ADC_RATE_22K (0x2 << 10) +#define FUNCTRL1_ADC_RATE_11K (0x1 << 10) +#define FUNCTRL1_ADC_RATE_5K (0x0 << 10) +#define FUNCTRL1_INTRM BIT(5) /* enable MCB intr */ +#define FUNCTRL1_BREQ BIT(4) /* bus master enable */ +#define FUNCTRL1_VOICE_EN BIT(3) +#define FUNCTRL1_UART_EN BIT(2) +#define FUNCTRL1_JYSTK_EN BIT(1) + +#define CHFORMAT_CH1_MASK (0x3 << 2) +#define CHFORMAT_CH1_16ST (0x3 << 2) +#define CHFORMAT_CH1_16MO (0x2 << 2) +#define CHFORMAT_CH1_8ST (0x1 << 2) +#define CHFORMAT_CH1_8MO (0x0 << 2) +#define CHFORMAT_CH0_MASK (0x3 << 0) +#define CHFORMAT_CH0_16ST (0x3 << 0) +#define CHFORMAT_CH0_16MO (0x2 << 0) +#define CHFORMAT_CH0_8ST (0x1 << 0) +#define CHFORMAT_CH0_8MO (0x0 << 0) + +#define INTCTRL_TDMA_EN BIT(18) +#define INTCTRL_CH1_EN BIT(17) +#define INTCTRL_CH0_EN BIT(16) + +#define INTSTAT_INTR BIT(31) +#define INTSTAT_MCB_INT BIT(26) +#define INTSTAT_UART_INT BIT(16) +#define INTSTAT_LTDMA_INT BIT(15) +#define INTSTAT_HTDMA_INT BIT(14) +#define INTSTAT_LHBTOG BIT(7) +#define INTSTAT_LEGDMA BIT(6) +#define INTSTAT_LEGHIGH BIT(5) +#define INTSTAT_LEGSTEREO BIT(4) +#define INTSTAT_CH1_BUSY BIT(3) +#define INTSTAT_CH0_BUSY BIT(2) +#define INTSTAT_CH1_INT BIT(1) +#define INTSTAT_CH0_INT BIT(0) + +#define MISC_PWD BIT(31) /* power down */ +#define MISC_RESET BIT(30) +#define MISC_ENDBDAC BIT(23) /* dual dac */ +#define MISC_XCHGDAC BIT(22) /* swap front/rear dacs */ +#define MISC_FM_EN BIT(19) /* enable legacy FM */ + +#define MIX2_FMMUTE BIT(7) +#define MIX2_WSMUTE BIT(6) +#define MIX2_WAVEIN_L BIT(3) /* for recording wave out */ +#define MIX2_WAVEIN_R BIT(2) /* for recording wave out */ + +#define MIX3_RAUXREN BIT(7) +#define MIX3_RAUXLEN BIT(6) +#define MIX3_VAUXRM BIT(5) /* r-aux mute */ +#define MIX3_VAUXLM BIT(4) /* l-aux mute */ +#define MIX3_VADCMIC_MASK (0x7 << 1) /* rec mic volume */ +#define MIX3_MICGAINZ BIT(0) /* mic gain */ + +#define VAUX_L_MASK 0xf0 +#define VAUX_R_MASK 0x0f + +/* Indexes via SBINDEX */ +#define IDX_MASTER_LEFT 0x30 +#define IDX_MASTER_RIGHT 0x31 +#define IDX_VOICE_LEFT 0x32 /* PCM volume */ +#define IDX_VOICE_RIGHT 0x33 +#define IDX_CDDA_LEFT 0x36 +#define IDX_CDDA_RIGHT 0x37 +#define IDX_LINEIN_LEFT 0x38 +#define IDX_LINEIN_RIGHT 0x39 +#define IDX_MIC 0x3A +#define IDX_SPEAKER 0x3B +#define IDX_OUTMIX 0x3C +#define OUTMIX_MIC 0x01 +#define OUTMIX_CD_R 0x02 +#define OUTMIX_CD_L 0x04 +#define OUTMIX_LINE_R 0x08 +#define OUTMIX_LINE_L 0x10 +#define IDX_INMIX_L 0x3D +#define IDX_INMIX_R 0x3E +#define INMIX_LINE_R 0x08 +#define INMIX_LINE_L 0x10 +#define INMIX_CD_R 0x20 +#define INMIX_CD_L 0x40 +#define INMIX_MIC 0x01 +#define IDX_IGAIN_L 0x3F +#define IDX_IGAIN_R 0x40 +#define IDX_OGAIN_L 0x41 +#define IDX_OGAIN_R 0x42 +#define IDX_AGC 0x43 +#define IDX_TREBLE_L 0x44 +#define IDX_TREBLE_R 0x45 +#define IDX_BASS_L 0x46 +#define IDX_BASS_R 0x47 + + +#define IDX_EXTENSION 0xf0 + +#define EXTENSION_VPHONE_MASK (0x7 << 5) +#define EXTENSION_VPHONE_MUTE BIT(4) +#define EXTENSION_BEEPER_MUTE BIT(3) +#define EXTENSION_VADCMIC3 BIT(0) + +enum { + SRC_MIC = 0, + SRC_LINE, + SRC_CD, + SRC_AUX, + SRC_MIX, +}; + +enum { + CTL_VOLUME = 0, + CTL_LINEOUT, + CTL_SPEAKER, + CTL_MIC, + CTL_LINEIN, + CTL_CD, + CTL_AUX, + CTL_RECSRCS, + CTL_MONSRCS, + CTL_MICBOOST, + CTL_NUM +}; + +typedef struct cmpci_port cmpci_port_t; +typedef struct cmpci_dev cmpci_dev_t; +typedef struct cmpci_ctrl cmpci_ctrl_t; + +struct cmpci_ctrl { + cmpci_dev_t *dev; + audio_ctrl_t *ctrl; + uint64_t value; +}; + +struct cmpci_port { + cmpci_dev_t *dev; + audio_engine_t *engine; + int num; + ddi_acc_handle_t acch; + ddi_dma_handle_t dmah; + caddr_t kaddr; + uint32_t paddr; + unsigned fragfr; + unsigned nfrags; + unsigned nframes; + unsigned bufsz; + + boolean_t capture; + boolean_t open; + + /* registers & bit masks */ + uint8_t reg_paddr; + uint8_t reg_bufsz; + uint8_t reg_fragsz; + + uint32_t fc0_rst_bit; + uint32_t fc0_rec_bit; + uint32_t fc0_en_bit; + uint32_t int_en_bit; + uint32_t fc1_rate_mask; + uint32_t chformat_mask; + int sync_dir; + + uint32_t offset; /* in bytes */ + uint64_t count; /* in bytes */ + + void (*callb)(audio_engine_t *); + cmpci_ctrl_t controls[CTL_NUM]; +}; + +#define PORT_MAX 2 + +struct cmpci_dev { + audio_dev_t *adev; + dev_info_t *dip; + ddi_acc_handle_t acch; + caddr_t regs; + + int pintrs; + int rintrs; + ddi_intr_handle_t ihandle; + kstat_t *ksp; + + int model; +#define MDL_CM8738 1 +#define MDL_CM8338A 2 +#define MDL_CM8338B 3 +#define MDL_CM8768 4 + char *chip_name; + int chiprev; + + boolean_t suspended; + + kmutex_t mutex; + cmpci_port_t port[PORT_MAX]; + cmpci_ctrl_t controls[CTL_NUM]; +}; + +/* + * The hardware appears to be able to address up to 16-bits worth of samples, + * giving a total address space of 256K. Note, however, that we will restrict + * this further when we do fragment and memory allocation. + */ +#define CMPCI_BUF_LEN (65536) +#define DEFINTS 175 + +#define GET8(dev, offset) \ + ddi_get8(dev->acch, (uint8_t *)(dev->regs + (offset))) +#define GET16(dev, offset) \ + ddi_get16(dev->acch, (uint16_t *)(void *)(dev->regs + (offset))) +#define GET32(dev, offset) \ + ddi_get32(dev->acch, (uint32_t *)(void *)(dev->regs + (offset))) +#define PUT8(dev, offset, v) \ + ddi_put8(dev->acch, (uint8_t *)(dev->regs + (offset)), v) +#define PUT16(dev, offset, v) \ + ddi_put16(dev->acch, (uint16_t *)(void *)(dev->regs + (offset)), v) +#define PUT32(dev, offset, v) \ + ddi_put32(dev->acch, (uint32_t *)(void *)(dev->regs + (offset)), v) + +#define CLR8(dev, offset, v) PUT8(dev, offset, GET8(dev, offset) & ~(v)) +#define SET8(dev, offset, v) PUT8(dev, offset, GET8(dev, offset) | (v)) +#define CLR16(dev, offset, v) PUT16(dev, offset, GET16(dev, offset) & ~(v)) +#define SET16(dev, offset, v) PUT16(dev, offset, GET16(dev, offset) | (v)) +#define CLR32(dev, offset, v) PUT32(dev, offset, GET32(dev, offset) & ~(v)) +#define SET32(dev, offset, v) PUT32(dev, offset, GET32(dev, offset) | (v)) + +#define KSINTR(dev) ((kstat_intr_t *)((dev)->ksp->ks_data)) + +#define BIT(n) (1U << (n)) + +#endif /* _AUDIOCMI_H */
--- a/usr/src/uts/intel/Makefile.intel.shared Tue Jun 09 07:24:43 2009 -0700 +++ b/usr/src/uts/intel/Makefile.intel.shared Tue Jun 09 11:09:06 2009 -0700 @@ -198,6 +198,7 @@ DRV_KMODS += audio DRV_KMODS += audio1575 DRV_KMODS += audio810 +DRV_KMODS += audiocmi DRV_KMODS += audioens DRV_KMODS += audiohd DRV_KMODS += audioixp
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/intel/audiocmi/Makefile Tue Jun 09 11:09:06 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/audiocmi/Makefile +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# This makefile drives the production of the audiocmi driver. +# + +# +# Path to the base of the uts directory tree (usually /usr/src/uts). +# +UTSBASE = ../.. + +# +# Define the module and object file sets. +# +MODULE = audiocmi +OBJECTS = $(AUDIOCMI_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(AUDIOCMI_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 + +# +# 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