/*
* 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);
}