--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/io/audio/drv/audiocmihd/audiocmihd.c Mon Jan 31 17:40:15 2011 -0800
@@ -0,0 +1,1804 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/*
+ * Purpose: Driver for the CMedia 8788 sound card
+ */
+/*
+ *
+ * Copyright (C) 4Front Technologies 1996-2011.
+ *
+ * This software is released under CDDL 1.0 source license.
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ */
+
+#include <sys/types.h>
+#include <sys/modctl.h>
+#include <sys/kmem.h>
+#include <sys/conf.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/pci.h>
+#include <sys/sysmacros.h>
+#include <sys/note.h>
+#include <sys/audio/audio_driver.h>
+#include <sys/audio/ac97.h>
+
+#include "audiocmihd.h"
+
+static struct ddi_device_acc_attr dev_attr = {
+ DDI_DEVICE_ATTR_V0,
+ DDI_STRUCTURE_LE_ACC,
+ DDI_STRICTORDER_ACC
+};
+
+static struct ddi_device_acc_attr buf_attr = {
+ DDI_DEVICE_ATTR_V0,
+ DDI_NEVERSWAP_ACC,
+ DDI_STRICTORDER_ACC
+};
+
+static ddi_dma_attr_t dma_attr_buf = {
+ DMA_ATTR_V0, /* version number */
+ 0x0, /* dma_attr_addr_lo */
+ 0xffffffffU, /* dma_attr_addr_hi */
+ 0x3ffff, /* dma_attr_count_max */
+ 0x8, /* dma_attr_align */
+ 0x7f, /* dma_attr_burstsizes */
+ 0x1, /* dma_attr_minxfer */
+ 0x3ffff, /* dma_attr_maxxfer */
+ 0x3ffff, /* dma_attr_seg */
+ 0x1, /* dma_attr_sgllen */
+ 0x1, /* dma_attr_granular */
+ 0 /* dma_attr_flags */
+};
+
+
+static int cmediahd_attach(dev_info_t *);
+static int cmediahd_resume(dev_info_t *);
+static int cmediahd_detach(cmediahd_devc_t *);
+static int cmediahd_suspend(cmediahd_devc_t *);
+
+static int cmediahd_open(void *, int, unsigned *, caddr_t *);
+static void cmediahd_close(void *);
+static int cmediahd_start(void *);
+static void cmediahd_stop(void *);
+static int cmediahd_format(void *);
+static int cmediahd_channels(void *);
+static int cmediahd_rate(void *);
+static uint64_t cmediahd_count(void *);
+static void cmediahd_sync(void *, unsigned);
+static void cmediahd_chinfo(void *, int, unsigned *, unsigned *);
+
+
+static uint16_t cmediahd_read_ac97(void *, uint8_t);
+static void cmediahd_write_ac97(void *, uint8_t, uint16_t);
+static int cmediahd_alloc_port(cmediahd_devc_t *, int);
+static void cmediahd_reset_port(cmediahd_portc_t *);
+static void cmediahd_destroy(cmediahd_devc_t *);
+static void cmediahd_hwinit(cmediahd_devc_t *);
+static void cmediahd_refresh_mixer(cmediahd_devc_t *devc);
+static uint32_t mix_scale(uint32_t, int8_t);
+static void cmediahd_ac97_hwinit(cmediahd_devc_t *);
+static void cmediahd_del_controls(cmediahd_devc_t *);
+
+
+static audio_engine_ops_t cmediahd_engine_ops = {
+ AUDIO_ENGINE_VERSION,
+ cmediahd_open,
+ cmediahd_close,
+ cmediahd_start,
+ cmediahd_stop,
+ cmediahd_count,
+ cmediahd_format,
+ cmediahd_channels,
+ cmediahd_rate,
+ cmediahd_sync,
+ NULL, /* qlen */
+ cmediahd_chinfo,
+ NULL /* playahead */
+};
+
+#define PLAYCTL (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_PLAY)
+#define RECCTL (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_REC)
+#define MONCTL (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_MONITOR)
+#define PCMVOL (PLAYCTL | AUDIO_CTRL_FLAG_PCMVOL)
+#define MAINVOL (PLAYCTL | AUDIO_CTRL_FLAG_MAINVOL)
+#define RECVOL (RECCTL | AUDIO_CTRL_FLAG_RECVOL)
+
+static const char mix_cvt[101] = {
+ 0, 0, 3, 7, 10, 13, 16, 19,
+ 21, 23, 26, 28, 30, 32, 34, 35,
+ 37, 39, 40, 42, 43, 45, 46, 47,
+ 49, 50, 51, 52, 53, 55, 56, 57,
+ 58, 59, 60, 61, 62, 63, 64, 65,
+ 65, 66, 67, 68, 69, 70, 70, 71,
+ 72, 73, 73, 74, 75, 75, 76, 77,
+ 77, 78, 79, 79, 80, 81, 81, 82,
+ 82, 83, 84, 84, 85, 85, 86, 86,
+ 87, 87, 88, 88, 89, 89, 90, 90,
+ 91, 91, 92, 92, 93, 93, 94, 94,
+ 95, 95, 96, 96, 96, 97, 97, 98,
+ 98, 98, 99, 99, 100
+};
+
+static uint32_t
+mix_scale(uint32_t vol, int8_t bits)
+{
+ vol = mix_cvt[vol];
+ vol = (vol * ((1 << bits) - 1)) / 100;
+ return (vol);
+}
+
+static uint16_t
+cmediahd_read_ac97(void *arg, uint8_t reg)
+{
+ cmediahd_devc_t *devc = arg;
+ uint32_t val;
+ uint16_t data;
+
+ mutex_enter(&devc->low_mutex);
+ val = 0L;
+ val |= reg << 16;
+ val |= 0 << 24; /* codec 0 or codec 1 */
+ val |= 1 << 23; /* ac97 read the reg address */
+ OUTL(devc, val, AC97_CMD_DATA);
+ drv_usecwait(100);
+ data = INL(devc, AC97_CMD_DATA) & 0xFFFF;
+ mutex_exit(&devc->low_mutex);
+ return (data);
+}
+
+static void
+cmediahd_write_ac97(void *arg, uint8_t reg, uint16_t data)
+{
+ cmediahd_devc_t *devc = arg;
+ uint32_t val;
+
+ mutex_enter(&devc->low_mutex);
+ val = 0L;
+ val |= reg << 16;
+ val |= data & 0xFFFF;
+ val |= 0 << 24; /* on board codec or frontpanel */
+ val |= 0 << 23; /* ac97 write operation */
+ OUTL(devc, val, AC97_CMD_DATA);
+ drv_usecwait(100);
+ mutex_exit(&devc->low_mutex);
+}
+
+#if 0 /* Front Panel AC'97 not supported yet */
+static uint16_t
+cmediahd_read_fp_ac97(void *arg, uint8_t reg)
+{
+ cmediahd_devc_t *devc = arg;
+ uint32_t val;
+ uint16_t data;
+
+ mutex_enter(&devc->low_mutex);
+ val = 0L;
+ val |= 1 << 24; /* front panel */
+ val |= 1 << 23; /* ac97 read the reg address */
+ val |= reg << 16;
+ OUTL(devc, val, AC97_CMD_DATA);
+ drv_usecwait(100);
+ data = INL(devc, AC97_CMD_DATA) & 0xFFFF;
+ mutex_exit(&devc->low_mutex);
+
+ return (data);
+}
+
+static void
+cmediahd_write_fp_ac97(void *arg, uint8_t reg, uint16_t data)
+{
+ cmediahd_devc_t *devc = arg;
+ uint32_t val;
+
+ mutex_enter(&devc->low_mutex);
+ val = 0L;
+ val |= 1 << 24; /* frontpanel */
+ val |= 0 << 23; /* ac97 write operation */
+ val |= reg << 16;
+ val |= data & 0xFFFF;
+ OUTL(devc, val, AC97_CMD_DATA);
+ drv_usecwait(100);
+ mutex_exit(&devc->low_mutex);
+}
+#endif
+
+static void
+spi_write(void *arg, int codec_num, unsigned char reg, int val)
+{
+ cmediahd_devc_t *devc = arg;
+ unsigned int tmp;
+ int latch, shift, count;
+
+ mutex_enter(&devc->low_mutex);
+
+ /* check if SPI is busy */
+ count = 10;
+ while ((INB(devc, SPI_CONTROL) & 0x1) && count-- > 0) {
+ drv_usecwait(10);
+ }
+
+ if (devc->model == SUBID_XONAR_DS) {
+ shift = 9;
+ latch = 0;
+ } else {
+ shift = 8;
+ latch = 0x80;
+ }
+
+ /* 2 byte data/reg info to be written */
+ tmp = val;
+ tmp |= (reg << shift);
+
+ /* write 2-byte data values */
+ OUTB(devc, tmp & 0xff, SPI_DATA + 0);
+ OUTB(devc, (tmp >> 8) & 0xff, SPI_DATA + 1);
+
+ /* Latch high, clock=160, Len=2byte, mode=write */
+ tmp = (INB(devc, SPI_CONTROL) & ~0x7E) | latch | 0x1;
+
+ /* now address which codec you want to send the data to */
+ tmp |= (codec_num << 4);
+
+ /* send the command to write the data */
+ OUTB(devc, tmp, SPI_CONTROL);
+
+ mutex_exit(&devc->low_mutex);
+}
+
+static void
+i2c_write(void *arg, unsigned char codec_num, unsigned char reg,
+ unsigned char data)
+{
+ cmediahd_devc_t *devc = arg;
+ int count = 50;
+
+ /* Wait for it to stop being busy */
+ mutex_enter(&devc->low_mutex);
+ while ((INW(devc, TWO_WIRE_CTRL) & 0x1) && (count > 0)) {
+ drv_usecwait(10);
+ count--;
+ }
+
+ if (count == 0) {
+ audio_dev_warn(devc->adev, "Time out on Two-Wire interface");
+ mutex_exit(&devc->low_mutex);
+ return;
+ }
+
+ /* first write the Register Address into the MAP register */
+ OUTB(devc, reg, TWO_WIRE_MAP);
+
+ /* now write the data */
+ OUTB(devc, data, TWO_WIRE_DATA);
+
+ /* select the codec number to address */
+ OUTB(devc, codec_num, TWO_WIRE_ADDR);
+
+ mutex_exit(&devc->low_mutex);
+}
+
+static void
+cs4398_init(void *arg, int codec)
+{
+ cmediahd_devc_t *devc = arg;
+
+ /* Fast Two-Wire. Reduces the wire ready time. */
+ OUTW(devc, 0x0100, TWO_WIRE_CTRL);
+
+ /* Power down, enable control mode. */
+ i2c_write(devc, codec, CS4398_MISC_CTRL,
+ CS4398_CPEN | CS4398_POWER_DOWN);
+ /*
+ * Left justified PCM (DAC and 8788 support I2S, but doesn't work.
+ * Setting it introduces clipping like hell).
+ */
+ i2c_write(devc, codec, CS4398_MODE_CTRL, 0x00);
+ i2c_write(devc, codec, 3, 0x09);
+ i2c_write(devc, codec, 4, 0x82); /* PCM Automute */
+ i2c_write(devc, codec, 5, 0x80); /* Vol A+B to -64dB */
+ i2c_write(devc, codec, 6, 0x80);
+ i2c_write(devc, codec, 7, 0xf0); /* soft ramping on */
+
+ /* remove the powerdown flag */
+ i2c_write(devc, codec, CS4398_MISC_CTRL, CS4398_CPEN);
+}
+
+
+static void
+cs4362a_init(void *arg, int codec)
+{
+
+ cmediahd_devc_t *devc = arg;
+
+ OUTW(devc, 0x0100, TWO_WIRE_CTRL);
+
+ /* Power down and enable control port. */
+ i2c_write(devc, codec, CS4362A_MODE1_CTRL,
+ CS4362A_CPEN | CS4362A_POWER_DOWN);
+ /* Left-justified PCM */
+ i2c_write(devc, codec, CS4362A_MODE2_CTRL, CS4362A_DIF_LJUST);
+ /* Ramp & Automute, re-set DAC defaults. */
+ i2c_write(devc, codec, CS4362A_MODE3_CTRL, 0x84);
+ /* Filter control, DAC defs. */
+ i2c_write(devc, codec, CS4362A_FILTER_CTRL, 0);
+ /* Invert control, DAC defs. */
+ i2c_write(devc, codec, CS4362A_INVERT_CTRL, 0);
+ /* Mixing control, DAC defs. */
+ i2c_write(devc, codec, CS4362A_MIX1_CTRL, 0x24);
+ i2c_write(devc, codec, CS4362A_MIX2_CTRL, 0x24);
+ i2c_write(devc, codec, CS4362A_MIX3_CTRL, 0x24);
+ /* Volume to -64dB. */
+ i2c_write(devc, codec, CS4362A_VOLA_1, 0x40);
+ i2c_write(devc, codec, CS4362A_VOLB_1, 0x40);
+ i2c_write(devc, codec, CS4362A_VOLA_2, 0x40);
+ i2c_write(devc, codec, CS4362A_VOLB_2, 0x40);
+ i2c_write(devc, codec, CS4362A_VOLA_3, 0x40);
+ i2c_write(devc, codec, CS4362A_VOLB_3, 0x40);
+ /* Power up. */
+ i2c_write(devc, codec, CS4362A_MODE1_CTRL, CS4362A_CPEN);
+}
+
+
+static void
+cmediahd_generic_set_play_volume(cmediahd_devc_t *devc, int codec_id,
+ int left, int right)
+
+{
+ spi_write(devc, codec_id, AK4396_LchATTCtl | 0x20, mix_scale(left, 8));
+ spi_write(devc, codec_id, AK4396_RchATTCtl | 0x20, mix_scale(right, 8));
+}
+
+static void
+xonar_d1_set_play_volume(cmediahd_devc_t *devc, int codec_id,
+ int left, int right)
+{
+ switch (codec_id) {
+ case 0:
+ i2c_write(devc, XONAR_DX_FRONTDAC, CS4398_VOLA,
+ CS4398_VOL(left));
+ i2c_write(devc, XONAR_DX_FRONTDAC, CS4398_VOLB,
+ CS4398_VOL(right));
+ break;
+ case 1:
+ i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLA_1,
+ CS4362A_VOL(left));
+ i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLB_1,
+ CS4362A_VOL(right));
+ break;
+ case 2:
+ i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLA_2,
+ CS4362A_VOL(left));
+ i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLB_2,
+ CS4362A_VOL(right));
+ break;
+ case 3:
+ i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLA_3,
+ CS4362A_VOL(left));
+ i2c_write(devc, XONAR_DX_SURRDAC, CS4362A_VOLB_3,
+ CS4362A_VOL(right));
+ break;
+ }
+}
+
+static void
+xonar_d2_set_play_volume(cmediahd_devc_t *devc, int codec_id,
+ int left, int right)
+{
+ spi_write(devc, xd2_codec_map[codec_id], 16, mix_scale(left, 8));
+ spi_write(devc, xd2_codec_map[codec_id], 17, mix_scale(right, 8));
+}
+
+static void
+xonar_stx_set_play_volume(cmediahd_devc_t *devc, int codec_id,
+ int left, int right)
+{
+ if (codec_id == 0) {
+ i2c_write(devc, XONAR_STX_FRONTDAC, 16, mix_scale(left, 8));
+ i2c_write(devc, XONAR_STX_FRONTDAC, 17, mix_scale(right, 8));
+ }
+}
+
+static void
+xonar_ds_set_play_volume(cmediahd_devc_t *devc, int codec_id,
+ int left, int right)
+{
+ switch (codec_id) {
+ case 0: /* front */
+ spi_write(devc, XONAR_DS_FRONTDAC, 0,
+ mix_scale(left, 7) | 0x180);
+ spi_write(devc, XONAR_DS_FRONTDAC, 1,
+ mix_scale(right, 7) | 0x180);
+ spi_write(devc, XONAR_DS_FRONTDAC, 3,
+ mix_scale(left, 7) |0x180);
+ spi_write(devc, XONAR_DS_FRONTDAC, 4,
+ mix_scale(right, 7) | 0x180);
+ break;
+
+ case 1: /* side */
+ spi_write(devc, XONAR_DS_SURRDAC, 0,
+ mix_scale(left, 7) | 0x180);
+ spi_write(devc, XONAR_DS_SURRDAC, 1,
+ mix_scale(right, 7) | 0x180);
+ break;
+ case 2: /* rear */
+ spi_write(devc, XONAR_DS_SURRDAC, 4,
+ mix_scale(left, 7) | 0x180);
+ spi_write(devc, XONAR_DS_SURRDAC, 5,
+ mix_scale(right, 7) | 0x180);
+ break;
+ case 3: /* center */
+ spi_write(devc, XONAR_DS_SURRDAC, 6,
+ mix_scale(left, 7) | 0x180);
+ spi_write(devc, XONAR_DS_SURRDAC, 7,
+ mix_scale(right, 7) | 0x180);
+ break;
+ }
+}
+
+static void
+cmediahd_set_rec_volume(cmediahd_devc_t *devc, int value)
+{
+ unsigned char left, right;
+
+ left = (value >> 8) & 0xff;
+ right = value & 0xff;
+
+ if (left > 100)
+ left = 100;
+ if (right > 100)
+ right = 100;
+
+ spi_write(devc, XONAR_DS_FRONTDAC, 0xe, mix_scale(left, 8));
+ spi_write(devc, XONAR_DS_FRONTDAC, 0xf, mix_scale(right, 8));
+}
+
+static void
+cmediahd_set_play_volume(cmediahd_devc_t *devc, int codec_id, int value)
+{
+ int left, right;
+
+ left = (value >> 8) & 0xFF;
+ right = (value & 0xFF);
+
+ if (left > 100)
+ left = 100;
+ if (right > 100)
+ right = 100;
+
+ switch (devc->model) {
+ case SUBID_XONAR_D1:
+ case SUBID_XONAR_DX:
+ xonar_d1_set_play_volume(devc, codec_id, left, right);
+ break;
+ case SUBID_XONAR_D2:
+ case SUBID_XONAR_D2X:
+ xonar_d2_set_play_volume(devc, codec_id, left, right);
+ break;
+ case SUBID_XONAR_STX:
+ xonar_stx_set_play_volume(devc, codec_id, left, right);
+ break;
+ case SUBID_XONAR_DS:
+ xonar_ds_set_play_volume(devc, codec_id, left, right);
+ break;
+ default:
+ cmediahd_generic_set_play_volume(devc, codec_id, left, right);
+ break;
+ }
+}
+
+/*
+ * Audio routines
+ */
+
+int
+cmediahd_open(void *arg, int flag, unsigned *nframesp, caddr_t *bufp)
+{
+ cmediahd_portc_t *portc = arg;
+
+ _NOTE(ARGUNUSED(flag));
+
+ portc->count = 0;
+
+ *nframesp = portc->nframes;
+ *bufp = portc->kaddr;
+
+ return (0);
+}
+
+void
+cmediahd_close(void *arg)
+{
+ _NOTE(ARGUNUSED(arg));
+}
+
+int
+cmediahd_start(void *arg)
+{
+ cmediahd_portc_t *portc = arg;
+ cmediahd_devc_t *devc = portc->devc;
+
+ mutex_enter(&devc->mutex);
+ portc->offset = 0;
+
+ cmediahd_reset_port(portc);
+
+ switch (portc->direction) {
+ case CMEDIAHD_PLAY:
+ /* enable the dma */
+ OUTW(devc, INW(devc, DMA_START) | 0x10, DMA_START);
+ break;
+
+ case CMEDIAHD_REC:
+ /* enable the channel */
+ OUTW(devc, INW(devc, DMA_START) | (1<<devc->rec_eng.chan),
+ DMA_START);
+ break;
+ }
+
+ mutex_exit(&devc->mutex);
+ return (0);
+}
+
+void
+cmediahd_stop(void *arg)
+{
+ cmediahd_portc_t *portc = arg;
+ cmediahd_devc_t *devc = portc->devc;
+
+ mutex_enter(&devc->mutex);
+ switch (portc->direction) {
+ case CMEDIAHD_PLAY:
+ /* disable dma */
+ OUTW(devc, INW(devc, DMA_START) & ~0x10, DMA_START);
+ break;
+
+ case CMEDIAHD_REC:
+ /* disable dma */
+ OUTW(devc, INW(devc, DMA_START) & ~(1<<devc->rec_eng.chan),
+ DMA_START);
+ break;
+ }
+ mutex_exit(&devc->mutex);
+}
+
+int
+cmediahd_format(void *arg)
+{
+ _NOTE(ARGUNUSED(arg));
+
+ return (AUDIO_FORMAT_S16_LE);
+}
+
+int
+cmediahd_channels(void *arg)
+{
+ cmediahd_portc_t *portc = arg;
+
+ return (portc->chans);
+}
+
+int
+cmediahd_rate(void *arg)
+{
+ _NOTE(ARGUNUSED(arg));
+
+ return (48000);
+}
+
+void
+cmediahd_sync(void *arg, unsigned nframes)
+{
+ cmediahd_portc_t *portc = arg;
+ _NOTE(ARGUNUSED(nframes));
+
+ (void) ddi_dma_sync(portc->buf_dmah, 0, 0, portc->syncdir);
+}
+
+static void
+cmediahd_chinfo(void *arg, int chan, unsigned *offset, unsigned *incr)
+{
+ cmediahd_portc_t *portc = arg;
+ static const int map8ch[] = { 0, 1, 4, 5, 2, 3, 6, 7 };
+ static const int map4ch[] = { 0, 1, 2, 3 };
+
+ if (portc->chans <= 4) {
+ *offset = map4ch[chan];
+ } else {
+ *offset = map8ch[chan];
+ }
+ *incr = portc->chans;
+}
+
+uint64_t
+cmediahd_count(void *arg)
+{
+ cmediahd_portc_t *portc = arg;
+ cmediahd_devc_t *devc = portc->devc;
+ uint64_t count;
+ uint32_t offset;
+
+ mutex_enter(&devc->mutex);
+
+ if (portc->direction == CMEDIAHD_PLAY)
+ offset = portc->bufsz/4 - INL(devc, MULTICH_SIZE) + 1;
+ else
+ offset = portc->bufsz/4 - INW(devc, devc->rec_eng.size) + 1;
+
+ /* check for wrap */
+ if (offset < portc->offset) {
+ count = ((portc->bufsz/4) - portc->offset) + offset;
+ } else {
+ count = offset - portc->offset;
+ }
+ portc->count += count;
+ portc->offset = offset;
+
+ /* convert from 16-bit stereo */
+ count = portc->count / (portc->chans/2);
+ mutex_exit(&devc->mutex);
+
+ return (count);
+}
+
+/* private implementation bits */
+
+
+void
+cmediahd_reset_port(cmediahd_portc_t *portc)
+{
+ cmediahd_devc_t *devc = portc->devc;
+ int channels;
+
+ if (devc->suspended)
+ return;
+
+ portc->offset = 0;
+
+ switch (portc->direction) {
+
+ case CMEDIAHD_PLAY:
+ /* reset channel */
+ OUTB(devc, INB(devc, CHAN_RESET)|0x10, CHAN_RESET);
+ drv_usecwait(10);
+ OUTB(devc, INB(devc, CHAN_RESET) & ~0x10, CHAN_RESET);
+ drv_usecwait(10);
+
+ OUTL(devc, portc->paddr, MULTICH_ADDR);
+ OUTL(devc, (portc->bufsz/4) - 1, MULTICH_SIZE);
+ OUTL(devc, (portc->bufsz/4) - 1, MULTICH_FRAG);
+
+ switch (portc->chans) {
+ case 2:
+ channels = 0;
+ break;
+ case 4:
+ channels = 1;
+ break;
+ case 6:
+ channels = 2;
+ break;
+ case 8:
+ channels = 3;
+ break;
+ }
+ OUTB(devc, (INB(devc, MULTICH_MODE) & ~0x3) | channels,
+ MULTICH_MODE);
+
+ /* set the format bits in play format register */
+ OUTB(devc, (INB(devc, PLAY_FORMAT) & ~0xC) | 0x0, PLAY_FORMAT);
+ break;
+
+ case CMEDIAHD_REC:
+ OUTB(devc, INB(devc, CHAN_RESET) | (1 << devc->rec_eng.chan),
+ CHAN_RESET);
+ drv_usecwait(10);
+ OUTB(devc, INB(devc, CHAN_RESET) & ~(1 << devc->rec_eng.chan),
+ CHAN_RESET);
+ drv_usecwait(10);
+
+ OUTL(devc, portc->paddr, devc->rec_eng.addr);
+ OUTW(devc, (portc->bufsz/4) - 1, devc->rec_eng.size);
+ OUTW(devc, (portc->bufsz/4) - 1, devc->rec_eng.frag);
+
+
+ switch (portc->chans) {
+ case 2:
+ channels = 0x0;
+ break;
+ case 4:
+ channels = 0x1;
+ break;
+ case 6:
+ channels = 0x2;
+ break;
+ case 8:
+ channels = 0x4;
+ break;
+ default:
+ /* Stereo - boomer only supports stereo */
+ channels = 0x0;
+ break;
+ }
+
+ OUTB(devc, (INB(devc, REC_MODE) & ~0x3) | channels, REC_MODE);
+ OUTB(devc, (INB(devc, REC_FORMAT) & ~0x3) | 0x0, REC_FORMAT);
+
+ }
+}
+
+int
+cmediahd_alloc_port(cmediahd_devc_t *devc, int num)
+{
+ cmediahd_portc_t *portc;
+ size_t len;
+ ddi_dma_cookie_t cookie;
+ uint_t count;
+ int dir;
+ unsigned caps;
+ audio_dev_t *adev;
+
+ adev = devc->adev;
+ portc = kmem_zalloc(sizeof (*portc), KM_SLEEP);
+ devc->portc[num] = portc;
+ portc->devc = devc;
+ portc->direction = num;
+
+ switch (num) {
+ case CMEDIAHD_REC:
+ portc->syncdir = DDI_DMA_SYNC_FORKERNEL;
+ portc->chans = 2;
+ caps = ENGINE_INPUT_CAP;
+ dir = DDI_DMA_READ;
+ break;
+ case CMEDIAHD_PLAY:
+ portc->syncdir = DDI_DMA_SYNC_FORDEV;
+ portc->chans = 8;
+ caps = ENGINE_OUTPUT_CAP;
+ dir = DDI_DMA_WRITE;
+ break;
+ default:
+ return (DDI_FAILURE);
+ }
+
+ /*
+ * Calculate buffer size and frames
+ */
+ portc->nframes = 2048;
+ portc->bufsz = portc->nframes * portc->chans * 2;
+
+ /* Alloc buffers */
+ if (ddi_dma_alloc_handle(devc->dip, &dma_attr_buf, DDI_DMA_SLEEP, NULL,
+ &portc->buf_dmah) != DDI_SUCCESS) {
+ audio_dev_warn(adev, "failed to allocate BUF handle");
+ return (DDI_FAILURE);
+ }
+
+ if (ddi_dma_mem_alloc(portc->buf_dmah, CMEDIAHD_BUF_LEN,
+ &buf_attr, DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL,
+ &portc->kaddr, &len, &portc->buf_acch) != DDI_SUCCESS) {
+ audio_dev_warn(adev, "failed to allocate BUF memory");
+ return (DDI_FAILURE);
+ }
+
+ bzero(portc->kaddr, len);
+
+ if (ddi_dma_addr_bind_handle(portc->buf_dmah, NULL, portc->kaddr,
+ len, DDI_DMA_CONSISTENT | dir, DDI_DMA_SLEEP, NULL, &cookie,
+ &count) != DDI_SUCCESS) {
+ audio_dev_warn(adev, "failed binding BUF DMA handle");
+ return (DDI_FAILURE);
+ }
+ portc->paddr = cookie.dmac_address;
+
+ portc->engine = audio_engine_alloc(&cmediahd_engine_ops, caps);
+ if (portc->engine == NULL) {
+ audio_dev_warn(adev, "audio_engine_alloc failed");
+ return (DDI_FAILURE);
+ }
+
+ audio_engine_set_private(portc->engine, portc);
+ audio_dev_add_engine(adev, portc->engine);
+
+ return (DDI_SUCCESS);
+}
+
+void
+cmediahd_destroy(cmediahd_devc_t *devc)
+{
+ mutex_destroy(&devc->mutex);
+ mutex_destroy(&devc->low_mutex);
+
+ for (int i = 0; i < CMEDIAHD_NUM_PORTC; i++) {
+ cmediahd_portc_t *portc = devc->portc[i];
+ if (!portc)
+ continue;
+ if (portc->engine) {
+ audio_dev_remove_engine(devc->adev, portc->engine);
+ audio_engine_free(portc->engine);
+ }
+ if (portc->paddr) {
+ (void) ddi_dma_unbind_handle(portc->buf_dmah);
+ }
+ if (portc->buf_acch) {
+ ddi_dma_mem_free(&portc->buf_acch);
+ }
+ if (portc->buf_dmah) {
+ ddi_dma_free_handle(&portc->buf_dmah);
+ }
+ kmem_free(portc, sizeof (*portc));
+ }
+
+ if (devc->ac97) {
+ ac97_free(devc->ac97);
+ }
+
+ cmediahd_del_controls(devc);
+
+ if (devc->adev != NULL) {
+ audio_dev_free(devc->adev);
+ }
+ if (devc->regsh != NULL) {
+ ddi_regs_map_free(&devc->regsh);
+ }
+ if (devc->pcih != NULL) {
+ pci_config_teardown(&devc->pcih);
+ }
+ kmem_free(devc, sizeof (*devc));
+}
+
+void
+cmediahd_ac97_hwinit(cmediahd_devc_t *devc)
+{
+ /* GPIO #0 programmed as output, set CMI9780 Reg0x70 */
+ cmediahd_write_ac97(devc, 0x70, 0x100);
+
+ /* LI2LI,MIC2MIC; let them always on, FOE on, ROE/BKOE/CBOE off */
+ cmediahd_write_ac97(devc, 0x62, 0x180F);
+
+ /* unmute Master Volume */
+ cmediahd_write_ac97(devc, 0x02, 0x0);
+
+ /* change PCBeep path, set Mix2FR on, option for quality issue */
+ cmediahd_write_ac97(devc, 0x64, 0x8043);
+
+ /* mute PCBeep, option for quality issues */
+ cmediahd_write_ac97(devc, 0x0A, 0x8000);
+
+ /* Record Select Control Register (Index 1Ah) */
+ cmediahd_write_ac97(devc, 0x1A, 0x0000);
+
+ /* set Mic Volume Register 0x0Eh umute and enable micboost */
+ cmediahd_write_ac97(devc, 0x0E, 0x0848);
+
+ /* set Line in Volume Register 0x10h mute */
+ cmediahd_write_ac97(devc, 0x10, 0x8808);
+
+ /* set CD Volume Register 0x12h mute */
+ cmediahd_write_ac97(devc, 0x12, 0x8808);
+
+ /* set AUX Volume Register 0x16h max */
+ cmediahd_write_ac97(devc, 0x16, 0x0808);
+
+ /* set record gain Register 0x1Ch to max */
+ cmediahd_write_ac97(devc, 0x1C, 0x0F0F);
+
+ /* GPIO status register enable GPO0 */
+ cmediahd_write_ac97(devc, 0x72, 0x0001);
+}
+void
+cmediahd_hwinit(cmediahd_devc_t *devc)
+{
+
+ unsigned short sVal;
+ unsigned short i2s_fmt;
+ unsigned char bVal;
+ int i, count;
+
+ /* setup the default rec DMA engines to REC_A */
+ devc->rec_eng.addr = RECA_ADDR;
+ devc->rec_eng.size = RECA_SIZE;
+ devc->rec_eng.frag = RECA_FRAG;
+ devc->rec_eng.i2s = I2S_ADC1;
+ devc->rec_eng.chan = REC_A;
+
+ /* setup GPIOs to 0 */
+ devc->gpio_mic = 0;
+ devc->gpio_out = 0;
+ devc->gpio_codec = 0;
+ devc->gpio_alt = 0;
+
+ /* Init CMI Controller */
+ sVal = INW(devc, CTRL_VERSION);
+ if (!(sVal & 0x0008)) {
+ bVal = INB(devc, MISC_REG);
+ bVal |= 0x20;
+ OUTB(devc, bVal, MISC_REG);
+ }
+
+ bVal = INB(devc, FUNCTION);
+ bVal |= 0x02; /* Reset codec */
+ OUTB(devc, bVal, FUNCTION);
+
+ /* Cold reset onboard AC97 */
+ OUTW(devc, 0x1, AC97_CTRL);
+ count = 100;
+ while ((INW(devc, AC97_CTRL) & 0x2) && (count--)) {
+ OUTW(devc, (INW(devc, AC97_CTRL) & ~0x2) | 0x2, AC97_CTRL);
+ drv_usecwait(100);
+ }
+
+ if (!count)
+ audio_dev_warn(devc->adev, "CMI8788 AC97 not ready");
+
+ sVal = INW(devc, AC97_CTRL);
+ /* check if there's an onboard AC97 codec (CODEC 0) */
+ if (sVal & 0x10) {
+ /* disable CODEC0 OUTPUT */
+ OUTW(devc, INW(devc, AC97_OUT_CHAN_CONFIG) & ~0xFF00,
+ AC97_OUT_CHAN_CONFIG);
+
+ /* enable CODEC0 INPUT */
+ OUTW(devc, INW(devc, AC97_IN_CHAN_CONFIG) | 0x0300,
+ AC97_IN_CHAN_CONFIG);
+
+ devc->has_ac97 = 1;
+ }
+
+ /* check if there's an front panel AC97 codec (CODEC1) */
+ if (sVal & 0x20) {
+ /* enable CODEC1 OUTPUT */
+ OUTW(devc, INW(devc, AC97_OUT_CHAN_CONFIG) | 0x0033,
+ AC97_OUT_CHAN_CONFIG);
+ /* enable CODEC1 INPUT */
+ OUTW(devc, INW(devc, AC97_IN_CHAN_CONFIG) | 0x0033,
+ AC97_IN_CHAN_CONFIG);
+
+ devc->has_fp_ac97 = 1;
+ }
+
+ /* Disable AC97 interrupts and initialize AC97 */
+ OUTB(devc, 0x0, AC97_INTR_MASK);
+ OUTW(devc, INW(devc, IRQ_MASK) & ~0x4000, IRQ_MASK);
+
+ /* I2S to 16bit/48Khz/Master, see below. */
+ i2s_fmt = 0x011A;
+
+ /* Setup I2S to use 16bit instead of 24Bit */
+ OUTW(devc, i2s_fmt, I2S_MULTICH_DAC);
+ OUTW(devc, i2s_fmt, I2S_ADC1);
+ OUTW(devc, i2s_fmt, I2S_ADC2);
+ OUTW(devc, i2s_fmt, I2S_ADC3);
+
+ /* setup Routing regs (default vals) */
+ OUTW(devc, 0xE400, PLAY_ROUTING);
+ OUTB(devc, 0x00, REC_ROUTING); /* default routing set to I2S */
+ OUTB(devc, 0x00, REC_MONITOR); /* monitor through MULTICH_PLAY */
+ OUTB(devc, 0xE4, MONITOR_ROUTING); /* default monitor routing */
+
+
+ /* Enable Xonar output */
+ switch (devc->model) {
+ case SUBID_XONAR_D1:
+ case SUBID_XONAR_DX:
+ /* GPIO8 = 0x100 controls mic/line-in */
+ /* GPIO0 = 0x001controls output */
+ /* GPIO2/3 = 0x00C codec output control */
+
+ devc->rec_eng.addr = RECB_ADDR;
+ devc->rec_eng.size = RECB_SIZE;
+ devc->rec_eng.frag = RECB_FRAG;
+ devc->rec_eng.i2s = I2S_ADC2;
+ devc->rec_eng.chan = REC_B;
+
+ /* disable AC97 mixer - not used */
+ devc->has_ac97 = 0;
+
+ /* setup for 2wire communication mode */
+ OUTB(devc, INB(devc, FUNCTION) | 0x40, FUNCTION);
+
+ /* setup GPIO direction */
+ OUTW(devc, INW(devc, GPIO_CONTROL) | 0x10D, GPIO_CONTROL);
+ /* setup GPIO pins */
+ OUTW(devc, INW(devc, GPIO_DATA) | 0x101, GPIO_DATA);
+
+ /* init the front and rear dacs */
+ cs4398_init(devc, XONAR_DX_FRONTDAC);
+ cs4362a_init(devc, XONAR_DX_SURRDAC);
+ break;
+
+ case SUBID_XONAR_D2:
+ case SUBID_XONAR_D2X:
+ /* GPIO7 = 0x0080 controls mic/line-in */
+ /* GPIO8 = 0x0100 controls output */
+ /* GPIO2/3 = 0x000C codec output control */
+
+ devc->rec_eng.addr = RECB_ADDR;
+ devc->rec_eng.size = RECB_SIZE;
+ devc->rec_eng.frag = RECB_FRAG;
+ devc->rec_eng.i2s = I2S_ADC2;
+ devc->rec_eng.chan = REC_B;
+
+ /* disable the AC97 mixer - it's not useful */
+ devc->has_ac97 = 0;
+
+ /* setup for spi communication mode */
+ OUTB(devc, (INB(devc, FUNCTION) & ~0x40) | 0x80, FUNCTION);
+ /* setup the GPIO direction */
+ OUTW(devc, INW(devc, GPIO_CONTROL) | 0x18c, GPIO_CONTROL);
+
+ /* setup GPIO Pins */
+ OUTW(devc, INW(devc, GPIO_DATA) | 0x100, GPIO_DATA);
+
+ /* for all 4 codecs: unmute, set to 24Bit SPI */
+ for (i = 0; i < 4; ++i) {
+ /* left vol */
+ spi_write(devc, i, 16, mix_scale(75, 8));
+ /* right vol */
+ spi_write(devc, i, 17, mix_scale(75, 8));
+ /* unmute/24LSB/ATLD */
+ spi_write(devc, i, 18, 0x30 | 0x80);
+ }
+ break;
+
+ case SUBID_XONAR_STX:
+ devc->rec_eng.addr = RECB_ADDR;
+ devc->rec_eng.size = RECB_SIZE;
+ devc->rec_eng.frag = RECB_FRAG;
+ devc->rec_eng.i2s = I2S_ADC2;
+ devc->rec_eng.chan = REC_B;
+
+ /* disable the AC97 mixer - it's not useful */
+ devc->has_ac97 = 0;
+
+ /* setup for spi communication mode */
+ OUTB(devc, (INB(devc, FUNCTION) & ~0x40) | 0x80, FUNCTION);
+ /* setup the GPIO direction */
+ OUTW(devc, INW(devc, GPIO_CONTROL) | 0x18F, GPIO_CONTROL);
+ /* setup GPIO Pins */
+ OUTW(devc, INW(devc, GPIO_DATA) | 0x111, GPIO_DATA);
+
+ /* init front DAC */
+ /* left vol */
+ i2c_write(devc, XONAR_STX_FRONTDAC, 16, mix_scale(75, 8));
+ /* right vol */
+ i2c_write(devc, XONAR_STX_FRONTDAC, 17, mix_scale(75, 8));
+ /* unmute/24LSB/ATLD */
+ i2c_write(devc, XONAR_STX_FRONTDAC, 18, 0x30 | 0x80);
+ i2c_write(devc, XONAR_STX_FRONTDAC, 19, 0); /* ATS1/FLT_SHARP */
+ i2c_write(devc, XONAR_STX_FRONTDAC, 20, 0); /* OS_64 */
+ i2c_write(devc, XONAR_STX_FRONTDAC, 21, 0);
+ break;
+
+ case SUBID_XONAR_DS:
+ /* GPIO 8 = 1 output enabled 0 mute */
+ /* GPIO 7 = 1 lineout enabled 0 mute */
+ /* GPIO 6 = 1 mic select 0 line-in select */
+ /* GPIO 4 = 1 FP Headphone plugged in */
+ /* GPIO 3 = 1 FP Mic plugged in */
+
+ devc->rec_eng.addr = RECA_ADDR;
+ devc->rec_eng.size = RECA_SIZE;
+ devc->rec_eng.frag = RECA_FRAG;
+ devc->rec_eng.i2s = I2S_ADC1;
+ devc->rec_eng.chan = REC_A;
+
+ /* disable the AC97 mixer - it's not useful */
+ devc->has_ac97 = 0;
+
+ /* setup for spi communication mode */
+ OUTB(devc, (INB(devc, FUNCTION) & ~0x40) | 0x80, FUNCTION);
+ /* setup the GPIO direction */
+ OUTW(devc, INW(devc, GPIO_CONTROL) | 0x1D0, GPIO_CONTROL);
+ /* setup GPIO Pins */
+ OUTW(devc, INW(devc, GPIO_DATA) | 0x1D0, GPIO_DATA);
+ spi_write(devc, XONAR_DS_FRONTDAC, 0x17, 0x1); /* reset */
+ spi_write(devc, XONAR_DS_FRONTDAC, 0x7, 0x90); /* dac control */
+ spi_write(devc, XONAR_DS_FRONTDAC, 0x8, 0); /* unmute */
+ /* powerdown hp */
+ spi_write(devc, XONAR_DS_FRONTDAC, 0xC, 0x22);
+ spi_write(devc, XONAR_DS_FRONTDAC, 0xD, 0x8); /* powerdown hp */
+ spi_write(devc, XONAR_DS_FRONTDAC, 0xA, 0x1); /* LJust/16bit */
+ spi_write(devc, XONAR_DS_FRONTDAC, 0xB, 0x1); /* LJust/16bit */
+ spi_write(devc, XONAR_DS_SURRDAC, 0x1f, 1); /* reset */
+ /* LJust/24bit */
+ spi_write(devc, XONAR_DS_SURRDAC, 0x3, 0x1|0x20);
+ break;
+
+
+ default:
+ /* SPI default for anything else, including the */
+ OUTB(devc, (INB(devc, FUNCTION) & ~0x40) | 0x80, FUNCTION);
+ OUTB(devc, 0x18, REC_ROUTING); /* default routing set to I2S */
+ break;
+ }
+
+ /* only initialize AC97 if not defined */
+ if (devc->has_ac97)
+ cmediahd_ac97_hwinit(devc);
+}
+
+static int
+cmediahd_set_control(void *arg, uint64_t val)
+{
+ cmediahd_ctrl_t *pc = arg;
+ cmediahd_devc_t *devc = pc->devc;
+
+ mutex_enter(&devc->mutex);
+
+ pc->val = val;
+
+ switch (pc->num) {
+
+ case CTL_VOLUME:
+ case CTL_FRONT:
+ cmediahd_set_play_volume(devc, 0, val);
+ break;
+
+ case CTL_REAR:
+ cmediahd_set_play_volume(devc, 1, val);
+ break;
+
+ case CTL_CENTER:
+ val &= 0xff;
+ val |= ((devc->controls[CTL_LFE].val) << 8);
+ cmediahd_set_play_volume(devc, 2, val);
+ break;
+
+ case CTL_LFE:
+ val &= 0xff;
+ val <<= 8;
+ val |= (devc->controls[CTL_CENTER].val);
+ cmediahd_set_play_volume(devc, 2, val);
+ break;
+
+ case CTL_SURROUND:
+ cmediahd_set_play_volume(devc, 3, val);
+ break;
+
+ case CTL_MONITOR:
+ /* enable recording monitor rec 1 and rec2 */
+ if (val)
+ OUTB(devc, INB(devc, REC_MONITOR) | 0xF, REC_MONITOR);
+ else
+ OUTB(devc, INB(devc, REC_MONITOR) & ~0xF, REC_MONITOR);
+ break;
+
+ case CTL_RECSRC:
+ switch (val) {
+ case 1: /* Line */
+ if (devc->model == SUBID_XONAR_DS)
+ OUTW(devc, INW(devc, GPIO_DATA) & ~0x40,
+ GPIO_DATA);
+
+ if (devc->model == SUBID_XONAR_D1 ||
+ devc->model == SUBID_XONAR_DX)
+ OUTW(devc, INW(devc, GPIO_DATA) &
+ ~devc->gpio_mic, GPIO_DATA);
+ cmediahd_write_ac97(devc, 0x72,
+ cmediahd_read_ac97(devc, 0x72) & ~0x1);
+ cmediahd_write_ac97(devc, 0x1A, 0x0404);
+ break;
+
+ case 2: /* Mic */
+ if (devc->model == SUBID_XONAR_DS)
+ OUTW(devc, INW(devc, GPIO_DATA) | 0x40,
+ GPIO_DATA);
+
+ if (devc->model == SUBID_XONAR_D1 ||
+ devc->model == SUBID_XONAR_DX)
+ OUTW(devc, INW(devc, GPIO_DATA) |
+ devc->gpio_mic, GPIO_DATA);
+ cmediahd_write_ac97(devc, 0x72,
+ cmediahd_read_ac97(devc, 0x72) | 0x1);
+ /* Unmute Mic */
+ cmediahd_write_ac97(devc, 0xE,
+ cmediahd_read_ac97(devc, 0xE) & ~0x8000);
+ /* Mute AUX and Video */
+ cmediahd_write_ac97(devc, 0x12,
+ cmediahd_read_ac97(devc, 0x12) | 0x8000);
+ cmediahd_write_ac97(devc, 0x16,
+ cmediahd_read_ac97(devc, 0x16) | 0x8000);
+ cmediahd_write_ac97(devc, 0x1A, 0x0000);
+ break;
+
+ case 4: /* AUX */
+ if (devc->model == SUBID_XONAR_D1 ||
+ devc->model == SUBID_XONAR_DX)
+ OUTW(devc, INW(devc, GPIO_DATA) |
+ devc->gpio_mic, GPIO_DATA);
+ cmediahd_write_ac97(devc, 0x72,
+ cmediahd_read_ac97(devc, 0x72) | 0x1);
+ /* Unmute AUX */
+ cmediahd_write_ac97(devc, 0x16,
+ cmediahd_read_ac97(devc, 0x16) & ~0x8000);
+ /* Mute CD and Mic */
+ cmediahd_write_ac97(devc, 0x14,
+ cmediahd_read_ac97(devc, 0x14) | 0x8000);
+ cmediahd_write_ac97(devc, 0x0E,
+ cmediahd_read_ac97(devc, 0x0E) | 0x8000);
+ cmediahd_write_ac97(devc, 0x1A, 0x0303);
+ break;
+
+ case 8: /* Video (CD) */
+ if (devc->model == SUBID_XONAR_D1 ||
+ devc->model == SUBID_XONAR_DX)
+ OUTW(devc, INW(devc, GPIO_DATA) |
+ devc->gpio_mic, GPIO_DATA);
+ cmediahd_write_ac97(devc, 0x72,
+ cmediahd_read_ac97(devc, 0x72) | 0x1);
+ /* Unmute Video (CD) */
+ cmediahd_write_ac97(devc, 0x14,
+ cmediahd_read_ac97(devc, 0x14) & ~0x8000);
+ /* Mute AUX and Mic */
+ cmediahd_write_ac97(devc, 0x16,
+ cmediahd_read_ac97(devc, 0x16) | 0x8000);
+ cmediahd_write_ac97(devc, 0x0E,
+ cmediahd_read_ac97(devc, 0x0E) | 0x8000);
+ /* set input to video */
+ cmediahd_write_ac97(devc, 0x1A, 0x0202);
+ break;
+ }
+ break;
+
+ case CTL_LOOP:
+ if (val)
+ OUTW(devc, INW(devc, GPIO_DATA) | devc->gpio_alt,
+ GPIO_DATA);
+ else
+ OUTW(devc, (INW(devc, GPIO_DATA) & ~devc->gpio_alt),
+ GPIO_DATA);
+ break;
+
+ case CTL_SPREAD:
+ if (val)
+ OUTW(devc, INW(devc, PLAY_ROUTING) & 0x00FF,
+ PLAY_ROUTING);
+ else
+ OUTW(devc, (INW(devc, PLAY_ROUTING) & 0x00FF) |
+ 0xE400, PLAY_ROUTING);
+ break;
+
+ case CTL_RECGAIN:
+ cmediahd_set_rec_volume(devc, val);
+ break;
+
+ case CTL_MICVOL:
+ if (val)
+ cmediahd_write_ac97(devc, 0x0E,
+ (0x40 | mix_scale(val, -5)) & ~0x8000);
+ else
+ cmediahd_write_ac97(devc, 0x0E, 0x8000);
+ break;
+
+ case CTL_AUXVOL:
+ if (val)
+ cmediahd_write_ac97(devc, 0x16,
+ mix_scale(val, -5) & ~0x8000);
+ else
+ cmediahd_write_ac97(devc, 0x16, 0x8000);
+ break;
+
+
+ case CTL_CDVOL:
+ if (val)
+ cmediahd_write_ac97(devc, 0x14,
+ mix_scale(val, -5) & ~0x8000);
+ else
+ cmediahd_write_ac97(devc, 0x14, 0x8000);
+ break;
+ }
+
+ mutex_exit(&devc->mutex);
+ return (0);
+}
+
+static int
+cmediahd_get_control(void *arg, uint64_t *val)
+{
+ cmediahd_ctrl_t *pc = arg;
+ cmediahd_devc_t *devc = pc->devc;
+
+ mutex_enter(&devc->mutex);
+ *val = pc->val;
+ mutex_exit(&devc->mutex);
+ return (0);
+}
+
+static void
+cmediahd_alloc_ctrl(cmediahd_devc_t *devc, uint32_t num, uint64_t val)
+{
+ audio_ctrl_desc_t desc;
+ cmediahd_ctrl_t *pc;
+
+ bzero(&desc, sizeof (desc));
+
+ pc = &devc->controls[num];
+ pc->num = num;
+ pc->devc = devc;
+
+
+ switch (num) {
+
+ case CTL_VOLUME:
+ desc.acd_name = AUDIO_CTRL_ID_VOLUME;
+ desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
+ desc.acd_minvalue = 0;
+ desc.acd_maxvalue = 100;
+ desc.acd_flags = PCMVOL;
+ break;
+
+ case CTL_FRONT:
+ desc.acd_name = AUDIO_CTRL_ID_FRONT;
+ desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
+ desc.acd_minvalue = 0;
+ desc.acd_maxvalue = 100;
+ desc.acd_flags = PCMVOL;
+ break;
+
+ case CTL_REAR:
+ desc.acd_name = AUDIO_CTRL_ID_REAR;
+ desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
+ desc.acd_minvalue = 0;
+ desc.acd_maxvalue = 100;
+ desc.acd_flags = PCMVOL;
+ break;
+
+ case CTL_SURROUND:
+ desc.acd_name = AUDIO_CTRL_ID_SURROUND;
+ desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
+ desc.acd_minvalue = 0;
+ desc.acd_maxvalue = 100;
+ desc.acd_flags = PCMVOL;
+ break;
+
+ case CTL_CENTER:
+ desc.acd_name = AUDIO_CTRL_ID_CENTER;
+ desc.acd_type = AUDIO_CTRL_TYPE_MONO;
+ desc.acd_minvalue = 0;
+ desc.acd_maxvalue = 100;
+ desc.acd_flags = PCMVOL;
+ break;
+
+ case CTL_LFE:
+ desc.acd_name = AUDIO_CTRL_ID_LFE;
+ desc.acd_type = AUDIO_CTRL_TYPE_MONO;
+ desc.acd_minvalue = 0;
+ desc.acd_maxvalue = 100;
+ desc.acd_flags = PCMVOL;
+ break;
+
+ case CTL_MONITOR:
+ desc.acd_name = AUDIO_CTRL_ID_MONSRC;
+ desc.acd_type = AUDIO_CTRL_TYPE_BOOLEAN;
+ desc.acd_minvalue = 0;
+ desc.acd_maxvalue = 1;
+ desc.acd_flags = RECCTL;
+ break;
+
+ case CTL_RECSRC:
+ desc.acd_name = AUDIO_CTRL_ID_RECSRC;
+ desc.acd_type = AUDIO_CTRL_TYPE_ENUM;
+ desc.acd_flags = RECCTL;
+ desc.acd_enum[0] = AUDIO_PORT_LINEIN;
+ desc.acd_enum[1] = AUDIO_PORT_MIC;
+
+ if (devc->model == SUBID_XONAR_D2 ||
+ devc->model == SUBID_XONAR_D2X) {
+ desc.acd_minvalue = 0xF;
+ desc.acd_maxvalue = 0xF;
+ desc.acd_enum[2] = AUDIO_PORT_AUX1IN;
+ desc.acd_enum[3] = AUDIO_PORT_CD;
+ } else {
+ desc.acd_minvalue = 0x3;
+ desc.acd_maxvalue = 0x3;
+ }
+ break;
+
+ case CTL_LOOP:
+ desc.acd_name = AUDIO_CTRL_ID_LOOPBACK;
+ desc.acd_type = AUDIO_CTRL_TYPE_BOOLEAN;
+ desc.acd_minvalue = 0;
+ desc.acd_maxvalue = 1;
+ desc.acd_flags = RECCTL;
+ break;
+
+ case CTL_SPREAD:
+ desc.acd_name = AUDIO_CTRL_ID_SPREAD;
+ desc.acd_type = AUDIO_CTRL_TYPE_BOOLEAN;
+ desc.acd_minvalue = 0;
+ desc.acd_maxvalue = 1;
+ desc.acd_flags = PLAYCTL;
+ break;
+
+ case CTL_RECGAIN:
+ desc.acd_name = AUDIO_CTRL_ID_RECGAIN;
+ desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
+ desc.acd_minvalue = 0;
+ desc.acd_maxvalue = 100;
+ desc.acd_flags = RECVOL;
+ break;
+
+ case CTL_MICVOL:
+ desc.acd_name = AUDIO_CTRL_ID_MIC;
+ desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
+ desc.acd_minvalue = 0;
+ desc.acd_maxvalue = 100;
+ desc.acd_flags = RECVOL;
+ break;
+
+ case CTL_AUXVOL:
+ desc.acd_name = AUDIO_CTRL_ID_AUX1IN;
+ desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
+ desc.acd_minvalue = 0;
+ desc.acd_maxvalue = 100;
+ desc.acd_flags = RECVOL;
+ break;
+ case CTL_CDVOL:
+ desc.acd_name = AUDIO_CTRL_ID_CD;
+ desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
+ desc.acd_minvalue = 0;
+ desc.acd_maxvalue = 100;
+ desc.acd_flags = RECVOL;
+ break;
+
+ }
+
+ pc->val = val;
+ pc->ctrl = audio_dev_add_control(devc->adev, &desc,
+ cmediahd_get_control, cmediahd_set_control, pc);
+}
+
+static void
+cmediahd_refresh_mixer(cmediahd_devc_t *devc)
+{
+ int ctl;
+
+ for (ctl = 0; ctl < CTL_NUM; ctl++) {
+ if (devc->controls[ctl].ctrl == NULL)
+ continue;
+ (void) cmediahd_set_control(&devc->controls[ctl],
+ devc->controls[ctl].val);
+ }
+}
+
+static void
+cmediahd_add_controls(cmediahd_devc_t *devc)
+{
+ cmediahd_alloc_ctrl(devc, CTL_VOLUME, 80 | (80 << 8));
+ cmediahd_alloc_ctrl(devc, CTL_FRONT, 80 | (80<<8));
+ cmediahd_alloc_ctrl(devc, CTL_REAR, 80 | (80<<8));
+ cmediahd_alloc_ctrl(devc, CTL_CENTER, 80);
+ cmediahd_alloc_ctrl(devc, CTL_LFE, 80);
+ cmediahd_alloc_ctrl(devc, CTL_SURROUND, 80 | (80<<8));
+ cmediahd_alloc_ctrl(devc, CTL_SPREAD, 0);
+ cmediahd_alloc_ctrl(devc, CTL_MONITOR, 0);
+ cmediahd_alloc_ctrl(devc, CTL_LOOP, 0);
+ cmediahd_alloc_ctrl(devc, CTL_RECSRC, 2);
+
+ switch (devc->model) {
+ case SUBID_XONAR_DS:
+ cmediahd_alloc_ctrl(devc, CTL_RECGAIN, 80|80<<8);
+ break;
+ case SUBID_XONAR_D2:
+ case SUBID_XONAR_D2X:
+ cmediahd_alloc_ctrl(devc, CTL_MICVOL, 80|80<<8);
+ cmediahd_alloc_ctrl(devc, CTL_AUXVOL, 80|80<<8);
+ cmediahd_alloc_ctrl(devc, CTL_CDVOL, 80|80<<8);
+ break;
+ }
+
+ cmediahd_refresh_mixer(devc);
+}
+
+void
+cmediahd_del_controls(cmediahd_devc_t *dev)
+{
+ for (int i = 0; i < CTL_NUM; i++) {
+ if (dev->controls[i].ctrl) {
+ audio_dev_del_control(dev->controls[i].ctrl);
+ dev->controls[i].ctrl = NULL;
+ }
+ }
+}
+
+int
+cmediahd_attach(dev_info_t *dip)
+{
+ uint16_t pci_command, vendor, device, subvendor, subdevice;
+ cmediahd_devc_t *devc;
+ ddi_acc_handle_t pcih;
+
+ devc = kmem_zalloc(sizeof (*devc), KM_SLEEP);
+ devc->dip = dip;
+ ddi_set_driver_private(dip, devc);
+
+ mutex_init(&devc->mutex, NULL, MUTEX_DRIVER, NULL);
+ mutex_init(&devc->low_mutex, NULL, MUTEX_DRIVER, NULL);
+
+ if ((devc->adev = audio_dev_alloc(dip, 0)) == NULL) {
+ cmn_err(CE_WARN, "audio_dev_alloc failed");
+ goto error;
+ }
+
+ if (pci_config_setup(dip, &pcih) != DDI_SUCCESS) {
+ audio_dev_warn(devc->adev, "pci_config_setup failed");
+ goto error;
+ }
+ devc->pcih = pcih;
+
+ vendor = pci_config_get16(pcih, PCI_CONF_VENID);
+ device = pci_config_get16(pcih, PCI_CONF_DEVID);
+ subvendor = pci_config_get16(pcih, PCI_CONF_SUBVENID);
+ subdevice = pci_config_get16(pcih, PCI_CONF_SUBSYSID);
+ if (vendor != PCI_VENDOR_ID_CMEDIA ||
+ device != PCI_DEVICE_ID_CMEDIAHD) {
+ audio_dev_warn(devc->adev, "Hardware not recognized "
+ "(vendor=%x, dev=%x)", vendor, device);
+ goto error;
+ }
+
+
+ pci_command = pci_config_get16(pcih, PCI_CONF_COMM);
+ pci_command |= PCI_COMM_ME | PCI_COMM_IO;
+ pci_config_put16(pcih, PCI_CONF_COMM, pci_command);
+
+ if ((ddi_regs_map_setup(dip, 1, &devc->base, 0, 0, &dev_attr,
+ &devc->regsh)) != DDI_SUCCESS) {
+ audio_dev_warn(devc->adev, "failed to map registers");
+ goto error;
+ }
+
+ audio_dev_set_description(devc->adev, "CMedia 8788");
+
+ /* Detect Xonar device */
+ if (subvendor == ASUS_VENDOR_ID) {
+ switch (subdevice) {
+ case SUBID_XONAR_D1:
+ audio_dev_set_description(devc->adev,
+ "Asus Xonar D1 (AV100)");
+ break;
+ case SUBID_XONAR_DX:
+ audio_dev_set_description(devc->adev,
+ "Asus Xonar DX (AV100)");
+ break;
+ case SUBID_XONAR_D2:
+ audio_dev_set_description(devc->adev,
+ "Asus Xonar D2 (AV200)");
+ break;
+ case SUBID_XONAR_D2X:
+ audio_dev_set_description(devc->adev,
+ "Asus Xonar D2X (AV200)");
+ break;
+ case SUBID_XONAR_STX:
+ audio_dev_set_description(devc->adev,
+ "Asus Xonar STX (AV100)");
+ break;
+ case SUBID_XONAR_DS:
+ audio_dev_set_description(devc->adev,
+ "Asus Xonar DS (AV66)");
+ break;
+ default:
+ audio_dev_set_description(devc->adev,
+ "Asus Xonar Unknown Model");
+ subdevice = SUBID_GENERIC;
+ break;
+ }
+ devc->model = subdevice;
+ }
+
+ cmediahd_hwinit(devc);
+
+ if (cmediahd_alloc_port(devc, CMEDIAHD_PLAY) != DDI_SUCCESS)
+ goto error;
+ if (cmediahd_alloc_port(devc, CMEDIAHD_REC) != DDI_SUCCESS)
+ goto error;
+
+ /* Add the AC97 Mixer if there is an onboard AC97 device */
+ if (devc->has_ac97) {
+ devc->ac97 = ac97_alloc(dip, cmediahd_read_ac97,
+ cmediahd_write_ac97, devc);
+ if (ac97_init(devc->ac97, devc->adev) != DDI_SUCCESS) {
+ audio_dev_warn(devc->adev, "failed to init ac97");
+ goto error;
+ }
+ }
+#if 0
+ /* Add the front panel AC97 device if one exists */
+ if (devc->has_fp_ac97) {
+ devc->fp_ac97 = ac97_alloc(dip, cmediahd_read_fp_ac97,
+ cmediahd_write_fp_ac97, devc);
+ if (ac97_init(devc->fp_ac97, devc->adev) != DDI_SUCCESS) {
+ audio_dev_warn(devc->adev, "failed to init fp_ac97");
+ goto error;
+ }
+ }
+#endif
+ /* Add the standard CMI8788 Mixer panel */
+ cmediahd_add_controls(devc);
+
+ if (audio_dev_register(devc->adev) != DDI_SUCCESS) {
+ audio_dev_warn(devc->adev, "unable to register with framework");
+ goto error;
+ }
+
+ ddi_report_dev(dip);
+
+ return (DDI_SUCCESS);
+
+error:
+ cmediahd_destroy(devc);
+ return (DDI_FAILURE);
+}
+
+int
+cmediahd_resume(dev_info_t *dip)
+{
+ cmediahd_devc_t *devc;
+
+ devc = ddi_get_driver_private(dip);
+
+ cmediahd_hwinit(devc);
+
+ if (devc->ac97)
+ ac97_reset(devc->ac97);
+
+ cmediahd_refresh_mixer(devc);
+
+ audio_dev_resume(devc->adev);
+
+ return (DDI_SUCCESS);
+}
+
+int
+cmediahd_detach(cmediahd_devc_t *devc)
+{
+ if (audio_dev_unregister(devc->adev) != DDI_SUCCESS)
+ return (DDI_FAILURE);
+
+ cmediahd_destroy(devc);
+ return (DDI_SUCCESS);
+}
+
+int
+cmediahd_suspend(cmediahd_devc_t *devc)
+{
+ audio_dev_suspend(devc->adev);
+ return (DDI_SUCCESS);
+}
+
+static int cmediahd_ddi_attach(dev_info_t *, ddi_attach_cmd_t);
+static int cmediahd_ddi_detach(dev_info_t *, ddi_detach_cmd_t);
+static int cmediahd_ddi_quiesce(dev_info_t *);
+
+static struct dev_ops cmediahd_dev_ops = {
+ DEVO_REV, /* rev */
+ 0, /* refcnt */
+ NULL, /* getinfo */
+ nulldev, /* identify */
+ nulldev, /* probe */
+ cmediahd_ddi_attach, /* attach */
+ cmediahd_ddi_detach, /* detach */
+ nodev, /* reset */
+ NULL, /* cb_ops */
+ NULL, /* bus_ops */
+ NULL, /* power */
+ cmediahd_ddi_quiesce, /* quiesce */
+};
+
+static struct modldrv cmediahd_modldrv = {
+ &mod_driverops, /* drv_modops */
+ "CMedia 8788", /* linkinfo */
+ &cmediahd_dev_ops, /* dev_ops */
+};
+
+static struct modlinkage modlinkage = {
+ MODREV_1,
+ { &cmediahd_modldrv, NULL }
+};
+
+int
+_init(void)
+{
+ int rv;
+
+ audio_init_ops(&cmediahd_dev_ops, CMEDIAHD_NAME);
+ if ((rv = mod_install(&modlinkage)) != 0) {
+ audio_fini_ops(&cmediahd_dev_ops);
+ }
+ return (rv);
+}
+
+int
+_fini(void)
+{
+ int rv;
+
+ if ((rv = mod_remove(&modlinkage)) == 0) {
+ audio_fini_ops(&cmediahd_dev_ops);
+ }
+ return (rv);
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+ return (mod_info(&modlinkage, modinfop));
+}
+
+int
+cmediahd_ddi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+ switch (cmd) {
+ case DDI_ATTACH:
+ return (cmediahd_attach(dip));
+
+ case DDI_RESUME:
+ return (cmediahd_resume(dip));
+
+ default:
+ return (DDI_FAILURE);
+ }
+}
+
+int
+cmediahd_ddi_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+ cmediahd_devc_t *devc;
+
+ devc = ddi_get_driver_private(dip);
+
+ switch (cmd) {
+ case DDI_DETACH:
+ return (cmediahd_detach(devc));
+
+ case DDI_SUSPEND:
+ return (cmediahd_suspend(devc));
+
+ default:
+ return (DDI_FAILURE);
+ }
+}
+
+int
+cmediahd_ddi_quiesce(dev_info_t *dip)
+{
+ cmediahd_devc_t *devc;
+
+ devc = ddi_get_driver_private(dip);
+
+ OUTW(devc, 0x0, DMA_START);
+
+ /*
+ * Turn off the hardware
+ */
+
+
+ return (DDI_SUCCESS);
+}