audioemu10k.c revision f9ead4a57883f3ef04ef20d83cc47987d98c0687
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Copyright (C) 4Front Technologies 1996-2009.
*/
/*
* Purpose: Driver for the Creative Sound Blaster Live! and Audigy/2/4
* sound cards
*/
#include <sys/types.h>
#include <sys/modctl.h>
#include <sys/kmem.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/pci.h>
#include <sys/note.h>
#include <sys/stdbool.h>
#include <sys/audio/audio_driver.h>
#include <sys/audio/ac97.h>
#include "audioemu10k.h"
#include <sys/promif.h>
/*
* Include the DSP files for emu10k1 (Live!) and emu10k2 (Audigy)
*/
#include "emu10k_gpr.h"
#include "emu10k1_dsp.h"
#include "emu10k2_dsp.h"
static struct ddi_device_acc_attr dev_attr = {
DDI_DEVICE_ATTR_V0,
DDI_STRUCTURE_LE_ACC,
DDI_STRICTORDER_ACC
};
static struct ddi_device_acc_attr buf_attr = {
DDI_DEVICE_ATTR_V0,
DDI_NEVERSWAP_ACC,
DDI_STRICTORDER_ACC
};
/*
* EMU10K routing stuff.
*/
#define MAX_SENDS 4
#define SEND_L 0
#define SEND_R 1
#define SEND_SURRL 2
#define SEND_SURRR 3
#define SEND_CEN 4
#define SEND_LFE 5
#define SEND_SIDEL 6
#define SEND_SIDER 7
#define SPDIF_L 20
#define SPDIF_R 21
/*
* Recording sources... we start from 16 to ensure that the
* record sources don't collide with AC'97 record sources in
* the control value.
*/
#define INPUT_AC97 1
#define INPUT_SPD1 2
#define INPUT_SPD2 3
#define INPUT_DIGCD 4
#define INPUT_AUX2 5
#define INPUT_LINE2 6
#define INPUT_STEREOMIX 7
static uint8_t front_routing[MAX_SENDS] = {
SEND_L, SEND_R, 0x3f, 0x3f
};
static uint8_t surr_routing[MAX_SENDS] = {
SEND_SURRL, SEND_SURRR, 0x3f, 0x3f
};
static uint8_t clfe_routing[MAX_SENDS] = {
SEND_CEN, SEND_LFE, 0x3f, 0x3f
};
static uint8_t side_routing[MAX_SENDS] = {
SEND_SIDEL, SEND_SIDER, 0x3f, 0x3f
};
static uint8_t no_routing[MAX_SENDS] = {
0x3f, 0x3f, 0x3f, 0x3f
};
/*
* SB Live! cannot do DMA above 2G addresses. Audigy/2/4 have special 8k page
* mode that supports high addresses. However, we should not need this except
* on SPARC. For simplicity's sake, we are only delivering this driver for
* x86 platforms. If SPARC support is desired, then the code will have to
* be modified to support full 32-bit addressing. (And again, SB Live!
* can't do it anyway.)
*/
static ddi_dma_attr_t dma_attr_buf = {
DMA_ATTR_V0, /* Version */
0x00000000ULL, /* Address low */
0x7ffffff0ULL, /* Address high */
0xffffffffULL, /* Counter max */
1ULL, /* Default byte align */
0x7f, /* Burst size */
0x1, /* Minimum xfer size */
0xffffffffULL, /* Maximum xfer size */
0xffffffffULL, /* Max segment size */
1, /* S/G list length */
1, /* Granularity */
0 /* Flag */
};
static int emu10k_attach(dev_info_t *);
static int emu10k_resume(dev_info_t *);
static int emu10k_detach(emu10k_devc_t *);
static int emu10k_suspend(emu10k_devc_t *);
static int emu10k_open(void *, int, unsigned *, unsigned *, caddr_t *);
static void emu10k_close(void *);
static int emu10k_start(void *);
static void emu10k_stop(void *);
static int emu10k_format(void *);
static int emu10k_channels(void *);
static int emu10k_rate(void *);
static uint64_t emu10k_count(void *);
static void emu10k_sync(void *, unsigned);
static void emu10k_chinfo(void *, int, unsigned *, unsigned *);
static uint16_t emu10k_read_ac97(void *, uint8_t);
static void emu10k_write_ac97(void *, uint8_t, uint16_t);
static int emu10k_alloc_port(emu10k_devc_t *, int);
static void emu10k_destroy(emu10k_devc_t *);
static int emu10k_setup_intrs(emu10k_devc_t *);
static int emu10k_hwinit(emu10k_devc_t *);
static uint_t emu10k_intr(caddr_t, caddr_t);
static void emu10k_init_effects(emu10k_devc_t *);
static audio_engine_ops_t emu10k_engine_ops = {
AUDIO_ENGINE_VERSION,
emu10k_open,
emu10k_close,
emu10k_start,
emu10k_stop,
emu10k_count,
emu10k_format,
emu10k_channels,
emu10k_rate,
emu10k_sync,
NULL,
emu10k_chinfo,
NULL
};
static uint16_t
emu10k_read_ac97(void *arg, uint8_t index)
{
emu10k_devc_t *devc = arg;
int dtemp = 0, i;
mutex_enter(&devc->mutex);
OUTB(devc, index, devc->regs + 0x1e);
for (i = 0; i < 10000; i++)
if (INB(devc, devc->regs + 0x1e) & 0x80)
break;
if (i == 1000) {
mutex_exit(&devc->mutex);
return (0); /* Timeout */
}
dtemp = INW(devc, devc->regs + 0x1c);
mutex_exit(&devc->mutex);
return (dtemp & 0xffff);
}
static void
emu10k_write_ac97(void *arg, uint8_t index, uint16_t data)
{
emu10k_devc_t *devc = arg;
int i;
mutex_enter(&devc->mutex);
OUTB(devc, index, devc->regs + 0x1e);
for (i = 0; i < 10000; i++)
if (INB(devc, devc->regs + 0x1e) & 0x80)
break;
OUTW(devc, data, devc->regs + 0x1c);
mutex_exit(&devc->mutex);
}
static uint32_t
emu10k_read_reg(emu10k_devc_t *devc, int reg, int chn)
{
uint32_t ptr, ptr_addr_mask, val, mask, size, offset;
ptr_addr_mask = (devc->feature_mask &
(SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL)) ?
0x0fff0000 : 0x07ff0000;
ptr = ((reg << 16) & ptr_addr_mask) | (chn & 0x3f);
OUTL(devc, ptr, devc->regs + 0x00); /* Pointer */
val = INL(devc, devc->regs + 0x04); /* Data */
if (reg & 0xff000000) {
size = (reg >> 24) & 0x3f;
offset = (reg >> 16) & 0x1f;
mask = ((1 << size) - 1) << offset;
val &= mask;
val >>= offset;
}
return (val);
}
static void
emu10k_write_reg(emu10k_devc_t *devc, int reg, int chn, uint32_t value)
{
uint32_t ptr, ptr_addr_mask, mask, size, offset;
ptr_addr_mask = (devc->feature_mask &
(SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL)) ?
0x0fff0000 : 0x07ff0000;
ptr = ((reg << 16) & ptr_addr_mask) | (chn & 0x3f);
OUTL(devc, ptr, devc->regs + 0x00); /* Pointer */
if (reg & 0xff000000) {
size = (reg >> 24) & 0x3f;
offset = (reg >> 16) & 0x1f;
mask = ((1 << size) - 1) << offset;
value <<= offset;
value &= mask;
value |= INL(devc, devc->regs + 0x04) & ~mask; /* data */
}
OUTL(devc, value, devc->regs + 0x04); /* Data */
}
static void
emu10k_write_routing(emu10k_devc_t *devc, int voice, unsigned char *routing)
{
int i;
ASSERT(routing != NULL);
if (devc->feature_mask & (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL)) {
unsigned int srda = 0;
for (i = 0; i < 4; i++)
srda |= routing[i] << (i * 8);
emu10k_write_reg(devc, SRDA, voice, srda);
} else {
int fxrt = 0;
for (i = 0; i < 4; i++)
fxrt |= routing[i] << ((i * 4) + 16);
emu10k_write_reg(devc, FXRT, voice, fxrt);
}
}
static void
emu10k_write_efx(emu10k_devc_t *devc, int reg, unsigned int value)
{
emu10k_write_reg(devc, reg, 0, value);
}
static uint_t
emu10k_intr(caddr_t argp, caddr_t nocare)
{
emu10k_devc_t *devc = (void *) argp;
emu10k_portc_t *portc;
uint32_t status;
audio_engine_t *cons = NULL, *prod = NULL;
_NOTE(ARGUNUSED (nocare));
mutex_enter(&devc->mutex);
if (devc->suspended) {
mutex_exit(&devc->mutex);
return (DDI_INTR_UNCLAIMED);
}
status = INL(devc, devc->regs + INTPEND);
if (status == 0) {
mutex_exit(&devc->mutex);
return (DDI_INTR_UNCLAIMED);
}
if (status & INT_CL) { /* channel loop */
emu10k_write_reg(devc, CLIPL, 0, (1U << 8));
OUTL(devc, INT_CL, devc->regs + INTPEND);
portc = devc->portc[EMU10K_PLAY];
if (portc->active) {
cons = portc->engine;
}
}
if (status & (INT_AF|INT_AH|INT_MF|INT_MH)) { /* ADC interrupt */
OUTL(devc, INT_AF|INT_AH|INT_MF|INT_MH, devc->regs + INTPEND);
portc = devc->portc[EMU10K_REC];
if (portc->active) {
prod = portc->engine;
}
}
mutex_exit(&devc->mutex);
if (cons) {
audio_engine_consume(cons);
}
if (prod) {
audio_engine_produce(prod);
}
return (DDI_INTR_CLAIMED);
}
/*
* Audio routines
*/
static void
emu10k_update_output_volume(emu10k_portc_t *portc, int voice, int chn)
{
emu10k_devc_t *devc = portc->devc;
unsigned int tmp;
unsigned char send[2];
/*
* Each voice operator of EMU10k has 4 sends (0=left, 1=right,
* 2=surround_left, 3=surround_right). The original OSS driver
* used all of them to spread stereo output to two different
* speaker pairs. This Boomer version uses only the first two
* sends. The other sends are set to 0.
*
* Boomer uses multiple voice pairs to play multichannel
* audio. This function is used to update only one of these
* pairs.
*/
send[0] = 0xff; /* Max */
send[1] = 0xff; /* Max */
/* Analog voice */
if (chn == LEFT_CH) {
send[1] = 0;
} else {
send[0] = 0;
}
tmp = emu10k_read_reg(devc, PTAB, voice) & 0xffff0000;
emu10k_write_reg(devc, PTAB, voice, tmp | (send[0] << 8) | send[1]);
}
static void
emu10k_setup_voice(emu10k_portc_t *portc, int voice, int chn, int buf_offset)
{
emu10k_devc_t *devc = portc->devc;
unsigned int nCRA = 0;
unsigned int loop_start, loop_end, buf_size;
int sz;
int start_pos;
emu10k_write_reg(devc, VEDS, voice, 0x0); /* OFF */
emu10k_write_reg(devc, VTFT, voice, 0xffff);
emu10k_write_reg(devc, CVCF, voice, 0xffff);
sz = 2; /* Shift value for 16 bits stereo */
/* Size of one stereo sub buffer */
buf_size = (portc->buf_size / portc->channels) * 2;
loop_start = (portc->memptr + buf_offset) >> sz;
loop_end = (portc->memptr + buf_offset + buf_size) >> sz;
/* set stereo */
emu10k_write_reg(devc, CPF, voice, 0x8000);
nCRA = 28; /* Stereo (16 bits) */
start_pos = loop_start + nCRA;
/* SDL, ST, CA */
emu10k_write_reg(devc, SDL, voice, loop_end);
emu10k_write_reg(devc, SCSA, voice, loop_start);
emu10k_write_reg(devc, PTAB, voice, 0);
emu10k_update_output_volume(portc, voice, chn); /* Set volume */
emu10k_write_reg(devc, QKBCA, voice, start_pos);
emu10k_write_reg(devc, Z1, voice, 0);
emu10k_write_reg(devc, Z2, voice, 0);
/* This is really a physical address */
emu10k_write_reg(devc, MAPA, voice,
0x1fff | (devc->silence_paddr << 1));
emu10k_write_reg(devc, MAPB, voice,
0x1fff | (devc->silence_paddr << 1));
emu10k_write_reg(devc, VTFT, voice, 0x0000ffff);
emu10k_write_reg(devc, CVCF, voice, 0x0000ffff);
emu10k_write_reg(devc, MEHA, voice, 0);
emu10k_write_reg(devc, MEDS, voice, 0x7f);
emu10k_write_reg(devc, MLV, voice, 0x8000);
emu10k_write_reg(devc, VLV, voice, 0x8000);
emu10k_write_reg(devc, VFM, voice, 0);
emu10k_write_reg(devc, TMFQ, voice, 0);
emu10k_write_reg(devc, VVFQ, voice, 0);
emu10k_write_reg(devc, MEV, voice, 0x8000);
emu10k_write_reg(devc, VEHA, voice, 0x7f7f); /* OK */
/* No volume envelope delay (OK) */
emu10k_write_reg(devc, VEV, voice, 0x8000);
emu10k_write_reg(devc, PEFE_FILTERAMOUNT, voice, 0x7f);
emu10k_write_reg(devc, PEFE_PITCHAMOUNT, voice, 0x00);
}
static void
emu10k_setup_silence(emu10k_portc_t *portc, int voice)
{
emu10k_devc_t *devc = portc->devc;
emu10k_write_reg(devc, VEDS, voice, 0x0); /* OFF */
emu10k_write_reg(devc, VTFT, voice, 0xffff);
emu10k_write_reg(devc, CVCF, voice, 0xffff);
/* set stereo */
emu10k_write_reg(devc, CPF, voice, 0x8000);
/* SDL, ST, CA */
emu10k_write_reg(devc, SDL, voice, portc->fragfr);
emu10k_write_reg(devc, SCSA, voice, 0);
emu10k_write_reg(devc, PTAB, voice, 0);
emu10k_write_reg(devc, QKBCA, voice, 0);
emu10k_write_reg(devc, Z1, voice, 0);
emu10k_write_reg(devc, Z2, voice, 0);
/* This is really a physical address */
emu10k_write_reg(devc, MAPA, voice,
0x1fff | (devc->silence_paddr << 1));
emu10k_write_reg(devc, MAPB, voice,
0x1fff | (devc->silence_paddr << 1));
emu10k_write_reg(devc, VTFT, voice, 0x0000ffff);
emu10k_write_reg(devc, CVCF, voice, 0x0000ffff);
emu10k_write_reg(devc, MEHA, voice, 0);
emu10k_write_reg(devc, MEDS, voice, 0x7f);
emu10k_write_reg(devc, MLV, voice, 0x8000);
emu10k_write_reg(devc, VLV, voice, 0x8000);
emu10k_write_reg(devc, VFM, voice, 0);
emu10k_write_reg(devc, TMFQ, voice, 0);
emu10k_write_reg(devc, VVFQ, voice, 0);
emu10k_write_reg(devc, MEV, voice, 0x8000);
emu10k_write_reg(devc, VEHA, voice, 0x7f7f); /* OK */
/* No volume envelope delay (OK) */
emu10k_write_reg(devc, VEV, voice, 0x8000);
emu10k_write_reg(devc, PEFE_FILTERAMOUNT, voice, 0x7f);
emu10k_write_reg(devc, PEFE_PITCHAMOUNT, voice, 0x00);
}
int
emu10k_open(void *arg, int flag,
unsigned *fragfrp, unsigned *nfragsp, caddr_t *bufp)
{
emu10k_portc_t *portc = arg;
emu10k_devc_t *devc = portc->devc;
_NOTE(ARGUNUSED(flag));
portc->started = B_FALSE;
portc->active = B_FALSE;
*fragfrp = portc->fragfr;
*nfragsp = portc->nfrags;
*bufp = portc->buf_kaddr;
mutex_enter(&devc->mutex);
if (!devc->suspended)
portc->reset_port(portc);
portc->count = 0;
mutex_exit(&devc->mutex);
return (0);
}
void
emu10k_close(void *arg)
{
emu10k_portc_t *portc = arg;
emu10k_devc_t *devc = portc->devc;
mutex_enter(&devc->mutex);
if (!devc->suspended)
portc->stop_port(portc);
portc->started = B_FALSE;
mutex_exit(&devc->mutex);
}
int
emu10k_start(void *arg)
{
emu10k_portc_t *portc = arg;
emu10k_devc_t *devc = portc->devc;
mutex_enter(&devc->mutex);
if (!portc->started) {
if (!devc->suspended)
portc->start_port(portc);
portc->started = B_TRUE;
}
mutex_exit(&devc->mutex);
return (0);
}
void
emu10k_stop(void *arg)
{
emu10k_portc_t *portc = arg;
emu10k_devc_t *devc = portc->devc;
mutex_enter(&devc->mutex);
if (portc->started) {
if (!devc->suspended)
portc->stop_port(portc);
portc->started = B_FALSE;
}
mutex_exit(&devc->mutex);
}
int
emu10k_format(void *arg)
{
_NOTE(ARGUNUSED(arg));
return (AUDIO_FORMAT_S16_LE);
}
int
emu10k_channels(void *arg)
{
emu10k_portc_t *portc = arg;
return (portc->channels);
}
int
emu10k_rate(void *arg)
{
_NOTE(ARGUNUSED(arg));
return (SAMPLE_RATE);
}
void
emu10k_sync(void *arg, unsigned nframes)
{
emu10k_portc_t *portc = arg;
_NOTE(ARGUNUSED(nframes));
(void) ddi_dma_sync(portc->buf_dmah, 0, 0, portc->syncdir);
}
uint64_t
emu10k_count(void *arg)
{
emu10k_portc_t *portc = arg;
emu10k_devc_t *devc = portc->devc;
uint64_t count;
mutex_enter(&devc->mutex);
if (!devc->suspended)
portc->update_port(portc);
count = portc->count;
mutex_exit(&devc->mutex);
return (count);
}
static void
emu10k_chinfo(void *arg, int chan, unsigned *offset, unsigned *incr)
{
emu10k_portc_t *portc = arg;
*offset = portc->nframes * (chan / 2) * 2 + (chan % 2);
*incr = 2;
}
/* private implementation bits */
static void
emu10k_set_loop_stop(emu10k_devc_t *devc, int voice, int s)
{
unsigned int tmp;
int offs, bit;
offs = voice / 32;
bit = voice % 32;
s = !!s;
tmp = emu10k_read_reg(devc, SOLL + offs, 0);
tmp &= ~(1 << bit);
if (s)
tmp |= (1 << bit);
emu10k_write_reg(devc, SOLL + offs, 0, tmp);
}
static unsigned int
emu10k_rate_to_pitch(unsigned int rate)
{
static unsigned int logMagTable[128] = {
0x00000, 0x02dfc, 0x05b9e, 0x088e6,
0x0b5d6, 0x0e26f, 0x10eb3, 0x13aa2,
0x1663f, 0x1918a, 0x1bc84, 0x1e72e,
0x2118b, 0x23b9a, 0x2655d, 0x28ed5,
0x2b803, 0x2e0e8, 0x30985, 0x331db,
0x359eb, 0x381b6, 0x3a93d, 0x3d081,
0x3f782, 0x41e42, 0x444c1, 0x46b01,
0x49101, 0x4b6c4, 0x4dc49, 0x50191,
0x5269e, 0x54b6f, 0x57006, 0x59463,
0x5b888, 0x5dc74, 0x60029, 0x623a7,
0x646ee, 0x66a00, 0x68cdd, 0x6af86,
0x6d1fa, 0x6f43c, 0x7164b, 0x73829,
0x759d4, 0x77b4f, 0x79c9a, 0x7bdb5,
0x7dea1, 0x7ff5e, 0x81fed, 0x8404e,
0x86082, 0x88089, 0x8a064, 0x8c014,
0x8df98, 0x8fef1, 0x91e20, 0x93d26,
0x95c01, 0x97ab4, 0x9993e, 0x9b79f,
0x9d5d9, 0x9f3ec, 0xa11d8, 0xa2f9d,
0xa4d3c, 0xa6ab5, 0xa8808, 0xaa537,
0xac241, 0xadf26, 0xafbe7, 0xb1885,
0xb3500, 0xb5157, 0xb6d8c, 0xb899f,
0xba58f, 0xbc15e, 0xbdd0c, 0xbf899,
0xc1404, 0xc2f50, 0xc4a7b, 0xc6587,
0xc8073, 0xc9b3f, 0xcb5ed, 0xcd07c,
0xceaec, 0xd053f, 0xd1f73, 0xd398a,
0xd5384, 0xd6d60, 0xd8720, 0xda0c3,
0xdba4a, 0xdd3b4, 0xded03, 0xe0636,
0xe1f4e, 0xe384a, 0xe512c, 0xe69f3,
0xe829f, 0xe9b31, 0xeb3a9, 0xecc08,
0xee44c, 0xefc78, 0xf148a, 0xf2c83,
0xf4463, 0xf5c2a, 0xf73da, 0xf8b71,
0xfa2f0, 0xfba57, 0xfd1a7, 0xfe8df
};
static char logSlopeTable[128] = {
0x5c, 0x5c, 0x5b, 0x5a, 0x5a, 0x59, 0x58, 0x58,
0x57, 0x56, 0x56, 0x55, 0x55, 0x54, 0x53, 0x53,
0x52, 0x52, 0x51, 0x51, 0x50, 0x50, 0x4f, 0x4f,
0x4e, 0x4d, 0x4d, 0x4d, 0x4c, 0x4c, 0x4b, 0x4b,
0x4a, 0x4a, 0x49, 0x49, 0x48, 0x48, 0x47, 0x47,
0x47, 0x46, 0x46, 0x45, 0x45, 0x45, 0x44, 0x44,
0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x41, 0x41,
0x41, 0x40, 0x40, 0x40, 0x3f, 0x3f, 0x3f, 0x3e,
0x3e, 0x3e, 0x3d, 0x3d, 0x3d, 0x3c, 0x3c, 0x3c,
0x3b, 0x3b, 0x3b, 0x3b, 0x3a, 0x3a, 0x3a, 0x39,
0x39, 0x39, 0x39, 0x38, 0x38, 0x38, 0x38, 0x37,
0x37, 0x37, 0x37, 0x36, 0x36, 0x36, 0x36, 0x35,
0x35, 0x35, 0x35, 0x34, 0x34, 0x34, 0x34, 0x34,
0x33, 0x33, 0x33, 0x33, 0x32, 0x32, 0x32, 0x32,
0x32, 0x31, 0x31, 0x31, 0x31, 0x31, 0x30, 0x30,
0x30, 0x30, 0x30, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f
};
int i;
if (rate == 0)
return (0); /* Bail out if no leading "1" */
rate *= 11185; /* Scale 48000 to 0x20002380 */
for (i = 31; i > 0; i--) {
if (rate & 0x80000000) { /* Detect leading "1" */
return (((unsigned int) (i - 15) << 20) +
logMagTable[0x7f & (rate >> 24)] +
(0x7f & (rate >> 17)) *
logSlopeTable[0x7f & (rate >> 24)]);
}
rate <<= 1;
}
return (0); /* Should never reach this point */
}
static unsigned int
emu10k_rate_to_linearpitch(unsigned int rate)
{
rate = (rate << 8) / 375;
return (rate >> 1) + (rate & 1);
}
static void
emu10k_prepare_voice(emu10k_devc_t *devc, int voice)
{
unsigned int sample, initial_pitch, pitch_target;
unsigned int cra, cs, ccis, i;
/* setup CCR regs */
cra = 64;
cs = 4; /* Stereo */
ccis = 28; /* Stereo */
sample = 0; /* 16 bit silence */
for (i = 0; i < cs; i++)
emu10k_write_reg(devc, CD0 + i, voice, sample);
emu10k_write_reg(devc, CCR_CACHEINVALIDSIZE, voice, 0);
emu10k_write_reg(devc, CCR_READADDRESS, voice, cra);
emu10k_write_reg(devc, CCR_CACHEINVALIDSIZE, voice, ccis);
/* Set current pitch */
emu10k_write_reg(devc, IFA, voice, 0xff00);
emu10k_write_reg(devc, VTFT, voice, 0xffffffff);
emu10k_write_reg(devc, CVCF, voice, 0xffffffff);
emu10k_set_loop_stop(devc, voice, 0);
pitch_target = emu10k_rate_to_linearpitch(SAMPLE_RATE);
initial_pitch = emu10k_rate_to_pitch(SAMPLE_RATE) >> 8;
emu10k_write_reg(devc, PTRX_PITCHTARGET, voice, pitch_target);
emu10k_write_reg(devc, CPF_CURRENTPITCH, voice, pitch_target);
emu10k_write_reg(devc, IP, voice, initial_pitch);
}
static void
emu10k_stop_voice(emu10k_devc_t *devc, int voice)
{
emu10k_write_reg(devc, IFA, voice, 0xffff);
emu10k_write_reg(devc, VTFT, voice, 0xffff);
emu10k_write_reg(devc, PTRX_PITCHTARGET, voice, 0);
emu10k_write_reg(devc, CPF_CURRENTPITCH, voice, 0);
emu10k_write_reg(devc, IP, voice, 0);
emu10k_set_loop_stop(devc, voice, 1);
}
static void
emu10k_reset_pair(emu10k_portc_t *portc, int voice, uint8_t *routing,
int buf_offset)
{
emu10k_devc_t *devc = portc->devc;
/* Left channel */
/* Intial filter cutoff and attenuation */
emu10k_write_reg(devc, IFA, voice, 0xffff);
/* Volume envelope decay and sustain */
emu10k_write_reg(devc, VEDS, voice, 0x0);
/* Volume target and Filter cutoff target */
emu10k_write_reg(devc, VTFT, voice, 0xffff);
/* Pitch target and sends A and B */
emu10k_write_reg(devc, PTAB, voice, 0x0);
/* The same for right channel */
emu10k_write_reg(devc, IFA, voice + 1, 0xffff);
emu10k_write_reg(devc, VEDS, voice + 1, 0x0);
emu10k_write_reg(devc, VTFT, voice + 1, 0xffff);
emu10k_write_reg(devc, PTAB, voice + 1, 0x0);
/* now setup the voices and go! */
emu10k_setup_voice(portc, voice, LEFT_CH, buf_offset);
emu10k_setup_voice(portc, voice + 1, RIGHT_CH, buf_offset);
emu10k_write_routing(devc, voice, routing);
emu10k_write_routing(devc, voice + 1, routing);
}
void
emu10k_start_play(emu10k_portc_t *portc)
{
emu10k_devc_t *devc = portc->devc;
ASSERT(mutex_owned(&devc->mutex));
emu10k_prepare_voice(devc, 0);
emu10k_prepare_voice(devc, 1);
emu10k_prepare_voice(devc, 2);
emu10k_prepare_voice(devc, 3);
emu10k_prepare_voice(devc, 4);
emu10k_prepare_voice(devc, 5);
emu10k_prepare_voice(devc, 6);
emu10k_prepare_voice(devc, 7);
emu10k_prepare_voice(devc, 8);
emu10k_prepare_voice(devc, 9);
/* arrange to receive full loop interrupts on channel 8 */
emu10k_write_reg(devc, CLIEL, 0, (1U << 8));
/* initialize our position counter... */
portc->pos =
(emu10k_read_reg(devc, QKBCA, 0) & 0xffffff) -
(portc->memptr >> 2);
/* Trigger playback on all voices */
emu10k_write_reg(devc, VEDS, 0, 0x7f7f);
emu10k_write_reg(devc, VEDS, 1, 0x7f7f);
emu10k_write_reg(devc, VEDS, 2, 0x7f7f);
emu10k_write_reg(devc, VEDS, 3, 0x7f7f);
emu10k_write_reg(devc, VEDS, 4, 0x7f7f);
emu10k_write_reg(devc, VEDS, 5, 0x7f7f);
emu10k_write_reg(devc, VEDS, 6, 0x7f7f);
emu10k_write_reg(devc, VEDS, 7, 0x7f7f);
emu10k_write_reg(devc, VEDS, 8, 0x7f7f);
emu10k_write_reg(devc, VEDS, 9, 0x7f7f);
portc->active = B_TRUE;
}
void
emu10k_stop_play(emu10k_portc_t *portc)
{
emu10k_devc_t *devc = portc->devc;
emu10k_stop_voice(devc, 0);
emu10k_stop_voice(devc, 1);
emu10k_stop_voice(devc, 2);
emu10k_stop_voice(devc, 3);
emu10k_stop_voice(devc, 4);
emu10k_stop_voice(devc, 5);
emu10k_stop_voice(devc, 6);
emu10k_stop_voice(devc, 7);
emu10k_stop_voice(devc, 8);
emu10k_stop_voice(devc, 9);
portc->active = B_FALSE;
}
void
emu10k_reset_play(emu10k_portc_t *portc)
{
emu10k_devc_t *devc = portc->devc;
uint32_t offs;
offs = (portc->buf_size / portc->channels) * 2;
if (devc->feature_mask & SB_71) {
emu10k_reset_pair(portc, 0, front_routing, 0);
emu10k_reset_pair(portc, 2, clfe_routing, offs);
emu10k_reset_pair(portc, 4, surr_routing, 2 * offs);
emu10k_reset_pair(portc, 6, side_routing, 3 * offs);
} else if (devc->feature_mask & SB_51) {
emu10k_reset_pair(portc, 0, front_routing, 0);
emu10k_reset_pair(portc, 2, clfe_routing, offs);
emu10k_reset_pair(portc, 4, surr_routing, 2 * offs);
} else {
emu10k_reset_pair(portc, 0, front_routing, 0);
emu10k_reset_pair(portc, 2, surr_routing, offs);
}
emu10k_setup_silence(portc, 8);
emu10k_setup_silence(portc, 9);
/*
* This way we can use voices 8 and 9 for timing, we have
* programmed them to be just the size of a single fragment,
* that way when they loop we get a clean interrupt.
*/
emu10k_write_routing(devc, 8, no_routing);
emu10k_write_routing(devc, 9, no_routing);
portc->pos = 0;
}
uint32_t emu10k_vars[5];
void
emu10k_update_play(emu10k_portc_t *portc)
{
emu10k_devc_t *devc = portc->devc;
uint32_t cnt, pos;
/*
* Note: position is given as stereo samples, i.e. frames.
*/
pos = emu10k_read_reg(devc, QKBCA, 0) & 0xffffff;
pos -= (portc->memptr >> 2);
if (pos <= portc->pos) {
cnt = portc->nframes - portc->pos;
cnt += pos;
} else {
cnt = (pos - portc->pos);
}
if (portc->dopos) {
emu10k_vars[0] = portc->pos;
emu10k_vars[1] = pos;
emu10k_vars[2] = (uint32_t)portc->count;
emu10k_vars[3] = cnt;
portc->dopos = 0;
}
if (cnt > portc->nframes) {
printf("Got bogus count %u\n", cnt);
cnt = portc->fragfr;
}
ASSERT(cnt <= portc->nframes);
portc->count += cnt;
portc->pos = pos;
}
void
emu10k_start_rec(emu10k_portc_t *portc)
{
emu10k_devc_t *devc = portc->devc;
uint32_t tmp;
/* Intr enable */
OUTL(devc, INL(devc, devc->regs + IE) | IE_MB | IE_AB,
devc->regs + IE);
tmp = 0; /* setup 48Kz */
if (devc->feature_mask & (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL))
tmp |= 0x30; /* Left/right channel enable */
else
tmp |= 0x18; /* Left/right channel enable */
emu10k_write_reg(devc, ADCSR, 0, tmp); /* GO */
portc->active = B_TRUE;
}
void
emu10k_stop_rec(emu10k_portc_t *portc)
{
emu10k_devc_t *devc = portc->devc;
ASSERT(mutex_owned(&devc->mutex));
emu10k_write_reg(devc, ADCSR, 0, 0);
portc->active = B_FALSE;
}
void
emu10k_reset_rec(emu10k_portc_t *portc)
{
emu10k_devc_t *devc = portc->devc;
uint32_t sz;
switch (portc->buf_size) {
case 4096:
sz = 15;
break;
case 8192:
sz = 19;
break;
case 16384:
sz = 23;
break;
case 32768:
sz = 27;
break;
case 65536:
sz = 31;
break;
}
emu10k_write_reg(devc, ADCBA, 0, portc->buf_paddr);
emu10k_write_reg(devc, ADCBS, 0, sz);
emu10k_write_reg(devc, ADCSR, 0, 0); /* reset for phase */
portc->pos = 0;
OUTL(devc, INL(devc, devc->regs + IE) & ~(IE_MB | IE_AB),
devc->regs + IE);
}
void
emu10k_update_rec(emu10k_portc_t *portc)
{
emu10k_devc_t *devc = portc->devc;
uint32_t cnt, pos;
/* given in bytes, we divide all counts by 4 to get samples */
pos = emu10k_read_reg(devc,
(devc->feature_mask & SB_LIVE) ? MIDX : ADCIDX, 0);
if (pos <= portc->pos) {
cnt = ((portc->buf_size) - portc->pos) >> 2;
cnt += (pos >> 2);
} else {
cnt = ((pos - portc->pos) >> 2);
}
portc->count += cnt;
portc->pos = pos;
}
int
emu10k_alloc_port(emu10k_devc_t *devc, int num)
{
emu10k_portc_t *portc;
size_t len;
ddi_dma_cookie_t cookie;
uint_t count;
int dir;
unsigned caps;
audio_dev_t *adev;
int i, n;
adev = devc->adev;
portc = kmem_zalloc(sizeof (*portc), KM_SLEEP);
devc->portc[num] = portc;
portc->devc = devc;
portc->started = B_FALSE;
portc->memptr = devc->audio_memptr;
devc->audio_memptr += (DMABUF_SIZE + 4095) & ~4095;
switch (num) {
case EMU10K_REC:
portc->syncdir = DDI_DMA_SYNC_FORKERNEL;
caps = ENGINE_INPUT_CAP;
dir = DDI_DMA_READ;
portc->channels = 2;
portc->start_port = emu10k_start_rec;
portc->stop_port = emu10k_stop_rec;
portc->reset_port = emu10k_reset_rec;
portc->update_port = emu10k_update_rec;
/* This is the minimum record buffer size. */
portc->buf_size = 4096;
portc->nfrags = 2;
portc->nframes = 4096 / 4;
portc->fragfr = portc->nframes / portc->nfrags;
break;
case EMU10K_PLAY:
portc->syncdir = DDI_DMA_SYNC_FORDEV;
caps = ENGINE_OUTPUT_CAP;
dir = DDI_DMA_WRITE;
portc->channels = 8;
portc->start_port = emu10k_start_play;
portc->stop_port = emu10k_stop_play;
portc->reset_port = emu10k_reset_play;
portc->update_port = emu10k_update_play;
/* XXX: this could probably be tunable */
portc->nfrags = 2;
portc->fragfr = 288;
portc->nframes = portc->nfrags * portc->fragfr;
portc->buf_size = portc->nframes * portc->channels * 2;
break;
default:
return (DDI_FAILURE);
}
/*
* Fragments that are not powers of two don't seem to work
* at all with EMU10K. For simplicity's sake, we eliminate
* the question and fix the interrupt rate. This is also the
* logical minimum for record, which requires at least 4K for
* the record size.
*/
if (portc->buf_size > DMABUF_SIZE) {
cmn_err(CE_NOTE, "Buffer size %d is too large (max %d)",
(int)portc->buf_size, DMABUF_SIZE);
portc->buf_size = DMABUF_SIZE;
}
/* Alloc buffers */
if (ddi_dma_alloc_handle(devc->dip, &dma_attr_buf, DDI_DMA_SLEEP, NULL,
&portc->buf_dmah) != DDI_SUCCESS) {
audio_dev_warn(adev, "failed to allocate BUF handle");
return (DDI_FAILURE);
}
if (ddi_dma_mem_alloc(portc->buf_dmah, portc->buf_size,
&dev_attr, DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL,
&portc->buf_kaddr, &len, &portc->buf_acch) != DDI_SUCCESS) {
audio_dev_warn(adev, "failed to allocate BUF memory");
return (DDI_FAILURE);
}
if (ddi_dma_addr_bind_handle(portc->buf_dmah, NULL, portc->buf_kaddr,
len, DDI_DMA_CONSISTENT | dir, DDI_DMA_SLEEP,
NULL, &cookie, &count) != DDI_SUCCESS) {
audio_dev_warn(adev, "failed binding BUF DMA handle");
return (DDI_FAILURE);
}
portc->buf_paddr = cookie.dmac_address;
if ((devc->feature_mask & SB_LIVE) &&
(portc->buf_paddr & 0x80000000)) {
audio_dev_warn(adev, "Got DMA buffer beyond 2G limit.");
return (DDI_FAILURE);
}
if (num == EMU10K_PLAY) { /* Output device */
n = portc->memptr / 4096;
/*
* Fill the page table
*/
for (i = 0; i < portc->buf_size / 4096; i++) {
FILL_PAGE_MAP_ENTRY(n + i,
portc->buf_paddr + i * 4096);
}
(void) ddi_dma_sync(devc->pt_dmah, 0, 0, DDI_DMA_SYNC_FORDEV);
}
portc->engine = audio_engine_alloc(&emu10k_engine_ops, caps);
if (portc->engine == NULL) {
audio_dev_warn(adev, "audio_engine_alloc failed");
return (DDI_FAILURE);
}
audio_engine_set_private(portc->engine, portc);
audio_dev_add_engine(adev, portc->engine);
return (DDI_SUCCESS);
}
int
emu10k_setup_intrs(emu10k_devc_t *devc)
{
uint_t ipri;
int actual;
int rv;
ddi_intr_handle_t ih[1];
rv = ddi_intr_alloc(devc->dip, ih, DDI_INTR_TYPE_FIXED,
0, 1, &actual, DDI_INTR_ALLOC_STRICT);
if ((rv != DDI_SUCCESS) || (actual != 1)) {
audio_dev_warn(devc->adev,
"Can't alloc interrupt handle (rv %d actual %d)",
rv, actual);
return (DDI_FAILURE);
}
if (ddi_intr_get_pri(ih[0], &ipri) != DDI_SUCCESS) {
audio_dev_warn(devc->adev, "Can't get interrupt priority");
(void) ddi_intr_free(ih[0]);
return (DDI_FAILURE);
}
if (ddi_intr_add_handler(ih[0], emu10k_intr, devc, NULL) !=
DDI_SUCCESS) {
audio_dev_warn(devc->adev, "Can't add interrupt handler");
(void) ddi_intr_free(ih[0]);
return (DDI_FAILURE);
}
devc->ih = ih[0];
mutex_init(&devc->mutex, NULL, MUTEX_DRIVER, DDI_INTR_PRI(ipri));
return (DDI_SUCCESS);
}
void
emu10k_destroy(emu10k_devc_t *devc)
{
if (devc->ih != NULL) {
(void) ddi_intr_disable(devc->ih);
(void) ddi_intr_remove_handler(devc->ih);
(void) ddi_intr_free(devc->ih);
mutex_destroy(&devc->mutex);
}
if (devc->ksp) {
kstat_delete(devc->ksp);
}
if (devc->silence_paddr) {
(void) ddi_dma_unbind_handle(devc->silence_dmah);
}
if (devc->silence_acch) {
ddi_dma_mem_free(&devc->silence_acch);
}
if (devc->silence_dmah) {
ddi_dma_free_handle(&devc->silence_dmah);
}
if (devc->pt_paddr) {
(void) ddi_dma_unbind_handle(devc->pt_dmah);
}
if (devc->pt_acch) {
ddi_dma_mem_free(&devc->pt_acch);
}
if (devc->pt_dmah) {
ddi_dma_free_handle(&devc->pt_dmah);
}
for (int i = 0; i < CTL_MAX; i++) {
emu10k_ctrl_t *ec = &devc->ctrls[i];
if (ec->ctrl != NULL) {
audio_dev_del_control(ec->ctrl);
ec->ctrl = NULL;
}
}
for (int i = 0; i < EMU10K_NUM_PORTC; i++) {
emu10k_portc_t *portc = devc->portc[i];
if (!portc)
continue;
if (portc->engine) {
audio_dev_remove_engine(devc->adev, portc->engine);
audio_engine_free(portc->engine);
}
if (portc->buf_paddr) {
(void) ddi_dma_unbind_handle(portc->buf_dmah);
}
if (portc->buf_acch) {
ddi_dma_mem_free(&portc->buf_acch);
}
if (portc->buf_dmah) {
ddi_dma_free_handle(&portc->buf_dmah);
}
kmem_free(portc, sizeof (*portc));
}
if (devc->ac97 != NULL) {
ac97_free(devc->ac97);
}
if (devc->adev != NULL) {
audio_dev_free(devc->adev);
}
if (devc->regsh != NULL) {
ddi_regs_map_free(&devc->regsh);
}
if (devc->pcih != NULL) {
pci_config_teardown(&devc->pcih);
}
kmem_free(devc, sizeof (*devc));
}
static void
emu10k_init_voice(emu10k_devc_t *devc, int voice)
{
emu10k_set_loop_stop(devc, voice, 1);
emu10k_write_reg(devc, VEDS, voice, 0x0);
emu10k_write_reg(devc, IP, voice, 0x0);
emu10k_write_reg(devc, VTFT, voice, 0xffff);
emu10k_write_reg(devc, CVCF, voice, 0xffff);
emu10k_write_reg(devc, PTAB, voice, 0x0);
emu10k_write_reg(devc, CPF, voice, 0x0);
emu10k_write_reg(devc, CCR, voice, 0x0);
emu10k_write_reg(devc, SCSA, voice, 0x0);
emu10k_write_reg(devc, SDL, voice, 0x10);
emu10k_write_reg(devc, QKBCA, voice, 0x0);
emu10k_write_reg(devc, Z1, voice, 0x0);
emu10k_write_reg(devc, Z2, voice, 0x0);
if (devc->feature_mask & (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL))
emu10k_write_reg(devc, SRDA, voice, 0x03020100);
else
emu10k_write_reg(devc, FXRT, voice, 0x32100000);
emu10k_write_reg(devc, MEHA, voice, 0x0);
emu10k_write_reg(devc, MEDS, voice, 0x0);
emu10k_write_reg(devc, IFA, voice, 0xffff);
emu10k_write_reg(devc, PEFE, voice, 0x0);
emu10k_write_reg(devc, VFM, voice, 0x0);
emu10k_write_reg(devc, TMFQ, voice, 24);
emu10k_write_reg(devc, VVFQ, voice, 24);
emu10k_write_reg(devc, TMPE, voice, 0x0);
emu10k_write_reg(devc, VLV, voice, 0x0);
emu10k_write_reg(devc, MLV, voice, 0x0);
emu10k_write_reg(devc, VEHA, voice, 0x0);
emu10k_write_reg(devc, VEV, voice, 0x0);
emu10k_write_reg(devc, MEV, voice, 0x0);
if (devc->feature_mask & (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL)) {
emu10k_write_reg(devc, CSBA, voice, 0x0);
emu10k_write_reg(devc, CSDC, voice, 0x0);
emu10k_write_reg(devc, CSFE, voice, 0x0);
emu10k_write_reg(devc, CSHG, voice, 0x0);
emu10k_write_reg(devc, SRHE, voice, 0x3f3f3f3f);
}
}
static void
emu10k_refresh_mixer(emu10k_devc_t *devc)
{
uint32_t val;
uint32_t set;
for (int gpr = 0; gpr < MAX_GPR; gpr++) {
if (devc->gpr_shadow[gpr].valid) {
emu10k_write_reg(devc, gpr + GPR0, 0,
devc->gpr_shadow[gpr].value);
}
}
set = devc->ctrls[CTL_JACK3].val;
if (devc->feature_mask & SB_INVSP) {
set = !set;
}
if (devc->feature_mask & (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL)) {
val = INL(devc, devc->regs + 0x18);
val &= ~A_IOCFG_GPOUT0;
val |= set ? 0x44 : 0x40;
OUTL(devc, val, devc->regs + 0x18);
} else if (devc->feature_mask & SB_LIVE) {
val = INL(devc, devc->regs + HCFG);
val &= ~HCFG_GPOUT0;
val |= set ? HCFG_GPOUT0 : 0;
OUTL(devc, val, devc->regs + HCFG);
}
}
int
emu10k_hwinit(emu10k_devc_t *devc)
{
unsigned int tmp, i;
unsigned int reg;
ASSERT(mutex_owned(&devc->mutex));
emu10k_write_reg(devc, AC97SLOT, 0, AC97SLOT_CENTER | AC97SLOT_LFE);
OUTL(devc, 0x00000000, devc->regs + 0x0c); /* Intr disable */
OUTL(devc, HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK |
HCFG_MUTEBUTTONENABLE,
devc->regs + HCFG);
emu10k_write_reg(devc, MBS, 0, 0x0);
emu10k_write_reg(devc, MBA, 0, 0x0);
emu10k_write_reg(devc, FXBS, 0, 0x0);
emu10k_write_reg(devc, FXBA, 0, 0x0);
emu10k_write_reg(devc, ADCBS, 0, 0x0);
emu10k_write_reg(devc, ADCBA, 0, 0x0);
OUTL(devc, 0, devc->regs + IE);
emu10k_write_reg(devc, CLIEL, 0, 0x0);
emu10k_write_reg(devc, CLIEH, 0, 0x0);
if (!(devc->feature_mask & SB_LIVE)) {
emu10k_write_reg(devc, HLIEL, 0, 0x0);
emu10k_write_reg(devc, HLIEH, 0, 0x0);
}
emu10k_write_reg(devc, CLIPL, 0, 0xffffffff);
emu10k_write_reg(devc, CLIPH, 0, 0xffffffff);
emu10k_write_reg(devc, SOLL, 0, 0xffffffff);
emu10k_write_reg(devc, SOLH, 0, 0xffffffff);
if (devc->feature_mask & (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL)) {
emu10k_write_reg(devc, SOC, 0, 0xf00); /* ?? */
emu10k_write_reg(devc, AC97SLOT, 0, 0x3); /* ?? */
}
for (i = 0; i < 64; i++)
emu10k_init_voice(devc, i);
emu10k_write_reg(devc, SCS0, 0, 0x2109204);
emu10k_write_reg(devc, SCS1, 0, 0x2109204);
emu10k_write_reg(devc, SCS2, 0, 0x2109204);
emu10k_write_reg(devc, PTBA, 0, devc->pt_paddr);
tmp = emu10k_read_reg(devc, PTBA, 0);
emu10k_write_reg(devc, TCBA, 0, 0x0);
emu10k_write_reg(devc, TCBS, 0, 0x4);
reg = 0;
if (devc->feature_mask & SB_71) {
reg = AC97SLOT_CENTER | AC97SLOT_LFE | AC97SLOT_REAR_LEFT |
AC97SLOT_REAR_RIGHT;
} else if (devc->feature_mask & SB_51) {
reg = AC97SLOT_CENTER | AC97SLOT_LFE;
}
if (devc->feature_mask & (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL))
reg |= 0x40;
emu10k_write_reg(devc, AC97SLOT, 0, reg);
if (devc->feature_mask & SB_AUDIGY2) {
/* Enable analog outputs on Audigy2 */
int tmp;
/* Setup SRCMulti_I2S SamplingRate */
tmp = emu10k_read_reg(devc, EHC, 0);
tmp &= 0xfffff1ff;
tmp |= (0x2 << 9);
emu10k_write_reg(devc, EHC, 0, tmp);
/* emu10k_write_reg (devc, SOC, 0, 0x00000000); */
/* Setup SRCSel (Enable Spdif,I2S SRCMulti) */
OUTL(devc, 0x600000, devc->regs + 0x20);
OUTL(devc, 0x14, devc->regs + 0x24);
/* Setup SRCMulti Input Audio Enable */
OUTL(devc, 0x6E0000, devc->regs + 0x20);
OUTL(devc, 0xFF00FF00, devc->regs + 0x24);
/* Setup I2S ASRC Enable (HC register) */
tmp = INL(devc, devc->regs + HCFG);
tmp |= 0x00000070;
OUTL(devc, tmp, devc->regs + HCFG);
/*
* Unmute Analog now. Set GPO6 to 1 for Apollo.
* This has to be done after init ALice3 I2SOut beyond 48KHz.
* So, sequence is important
*/
tmp = INL(devc, devc->regs + 0x18);
tmp |= 0x0040;
OUTL(devc, tmp, devc->regs + 0x18);
}
if (devc->feature_mask & SB_AUDIGY2VAL) {
/* Enable analog outputs on Audigy2 */
int tmp;
/* Setup SRCMulti_I2S SamplingRate */
tmp = emu10k_read_reg(devc, EHC, 0);
tmp &= 0xfffff1ff;
tmp |= (0x2 << 9);
emu10k_write_reg(devc, EHC, 0, tmp);
/* Setup SRCSel (Enable Spdif,I2S SRCMulti) */
OUTL(devc, 0x600000, devc->regs + 0x20);
OUTL(devc, 0x14, devc->regs + 0x24);
/* Setup SRCMulti Input Audio Enable */
OUTL(devc, 0x7B0000, devc->regs + 0x20);
OUTL(devc, 0xFF000000, devc->regs + 0x24);
/* SPDIF output enable */
OUTL(devc, 0x7A0000, devc->regs + 0x20);
OUTL(devc, 0xFF000000, devc->regs + 0x24);
tmp = INL(devc, devc->regs + 0x18) & ~0x8;
OUTL(devc, tmp, devc->regs + 0x18);
}
emu10k_write_reg(devc, SOLL, 0, 0xffffffff);
emu10k_write_reg(devc, SOLH, 0, 0xffffffff);
if (devc->feature_mask & (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL)) {
unsigned int mode = 0;
if (devc->feature_mask & (SB_AUDIGY2|SB_AUDIGY2VAL))
mode |= HCFG_AC3ENABLE_GPSPDIF | HCFG_AC3ENABLE_CDSPDIF;
OUTL(devc,
HCFG_AUDIOENABLE | HCFG_AUTOMUTE |
HCFG_JOYENABLE | A_HCFG_VMUTE |
A_HCFG_AUTOMUTE | mode, devc->regs + HCFG);
OUTL(devc, INL(devc, devc->regs + 0x18) |
0x0004, devc->regs + 0x18); /* GPIO (S/PDIF enable) */
/* enable IR port */
tmp = INL(devc, devc->regs + 0x18);
OUTL(devc, tmp | A_IOCFG_GPOUT2, devc->regs + 0x18);
drv_usecwait(500);
OUTL(devc, tmp | A_IOCFG_GPOUT1 | A_IOCFG_GPOUT2,
devc->regs + 0x18);
drv_usecwait(100);
OUTL(devc, tmp, devc->regs + 0x18);
} else {
OUTL(devc,
HCFG_AUDIOENABLE | HCFG_LOCKTANKCACHE_MASK |
HCFG_AUTOMUTE | HCFG_JOYENABLE, devc->regs + HCFG);
}
/* enable IR port */
tmp = INL(devc, devc->regs + HCFG);
OUTL(devc, tmp | HCFG_GPOUT2, devc->regs + HCFG);
drv_usecwait(500);
OUTL(devc, tmp | HCFG_GPOUT1 | HCFG_GPOUT2, devc->regs + HCFG);
drv_usecwait(100);
OUTL(devc, tmp, devc->regs + HCFG);
/*
* Start by configuring for analog mode.
*/
if (devc->feature_mask & (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL)) {
reg = INL(devc, devc->regs + 0x18) & ~A_IOCFG_GPOUT0;
reg |= ((devc->feature_mask & SB_INVSP) ? 0x4 : 0);
OUTL(devc, reg, devc->regs + 0x18);
}
if (devc->feature_mask & SB_LIVE) { /* SBLIVE */
reg = INL(devc, devc->regs + HCFG) & ~HCFG_GPOUT0;
reg |= ((devc->feature_mask & SB_INVSP) ? HCFG_GPOUT0 : 0);
OUTL(devc, reg, devc->regs + HCFG);
}
if (devc->feature_mask & SB_AUDIGY2VAL) {
OUTL(devc, INL(devc, devc->regs + 0x18) | 0x0060,
devc->regs + 0x18);
} else if (devc->feature_mask & SB_AUDIGY2) {
OUTL(devc, INL(devc, devc->regs + 0x18) | 0x0040,
devc->regs + 0x18);
} else if (devc->feature_mask & SB_AUDIGY) {
OUTL(devc, INL(devc, devc->regs + 0x18) | 0x0080,
devc->regs + 0x18);
}
emu10k_init_effects(devc);
return (DDI_SUCCESS);
}
static const int db2lin_101[101] = {
0x00000000,
0x0024B53A, 0x002750CA, 0x002A1BC6, 0x002D198D, 0x00304DBA, 0x0033BC2A,
0x00376901, 0x003B58AF, 0x003F8FF1, 0x004413DF, 0x0048E9EA, 0x004E17E9,
0x0053A419, 0x0059952C, 0x005FF24E, 0x0066C32A, 0x006E0FFB, 0x0075E18D,
0x007E414F, 0x0087395B, 0x0090D482, 0x009B1E5B, 0x00A6234F, 0x00B1F0A7,
0x00BE94A1, 0x00CC1E7C, 0x00DA9E8D, 0x00EA2650, 0x00FAC881, 0x010C9931,
0x011FADDC, 0x01341D87, 0x014A00D8, 0x01617235, 0x017A8DE6, 0x01957233,
0x01B23F8D, 0x01D118B1, 0x01F222D4, 0x021585D1, 0x023B6C57, 0x0264041D,
0x028F7E19, 0x02BE0EBD, 0x02EFEE33, 0x032558A2, 0x035E8E7A, 0x039BD4BC,
0x03DD7551, 0x0423BF61, 0x046F07B5, 0x04BFA91B, 0x051604D5, 0x0572830D,
0x05D59354, 0x063FAD27, 0x06B15080, 0x072B0673, 0x07AD61CD, 0x0838FFCA,
0x08CE88D3, 0x096EB147, 0x0A1A3A53, 0x0AD1F2E0, 0x0B96B889, 0x0C6978A5,
0x0D4B316A, 0x0E3CF31B, 0x0F3FE155, 0x10553469, 0x117E3AD9, 0x12BC5AEA,
0x14111457, 0x157E0219, 0x1704DC5E, 0x18A77A97, 0x1A67D5B6, 0x1C480A87,
0x1E4A5C45, 0x2071374D, 0x22BF3412, 0x25371A37, 0x27DBE3EF, 0x2AB0C18F,
0x2DB91D6F, 0x30F89FFD, 0x34733433, 0x382D0C46, 0x3C2AA6BD, 0x4070D3D9,
0x4504BB66, 0x49EBE2F1, 0x4F2C346F, 0x54CC0565, 0x5AD21E86, 0x6145C3E7,
0x682EBDBD, 0x6F9561C4, 0x77829D4D,
0x7fffffff
};
static int
emu10k_convert_fixpoint(int val)
{
if (val < 0)
val = 0;
if (val > 100)
val = 100;
return (db2lin_101[val]);
}
static void
emu10k_write_gpr(emu10k_devc_t *devc, int gpr, uint32_t value)
{
ASSERT(gpr < MAX_GPR);
devc->gpr_shadow[gpr].valid = B_TRUE;
devc->gpr_shadow[gpr].value = value;
if (!devc->suspended) {
emu10k_write_reg(devc, gpr + GPR0, 0, value);
}
}
static int
emu10k_set_stereo(void *arg, uint64_t val)
{
emu10k_ctrl_t *ec = arg;
emu10k_devc_t *devc = ec->devc;
uint32_t left, right;
left = (val >> 8) & 0xff;
right = val & 0xff;
if ((left > 100) || (right > 100) || (val & ~(0xffff)))
return (EINVAL);
left = emu10k_convert_fixpoint(left);
right = emu10k_convert_fixpoint(right);
mutex_enter(&devc->mutex);
ec->val = val;
emu10k_write_gpr(devc, ec->gpr_num, left);
emu10k_write_gpr(devc, ec->gpr_num + 1, right);
mutex_exit(&devc->mutex);
return (0);
}
static int
emu10k_set_mono(void *arg, uint64_t val)
{
emu10k_ctrl_t *ec = arg;
emu10k_devc_t *devc = ec->devc;
uint32_t v;
if (val > 100)
return (EINVAL);
v = emu10k_convert_fixpoint(val & 0xff);
mutex_enter(&devc->mutex);
ec->val = val;
emu10k_write_gpr(devc, ec->gpr_num, v);
mutex_exit(&devc->mutex);
return (0);
}
static int
emu10k_get_control(void *arg, uint64_t *val)
{
emu10k_ctrl_t *ec = arg;
emu10k_devc_t *devc = ec->devc;
mutex_enter(&devc->mutex);
*val = ec->val;
mutex_exit(&devc->mutex);
return (0);
}
#define PLAYCTL (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_PLAY)
#define RECCTL (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_REC)
#define MONCTL (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_MONITOR)
#define MAINVOL (PLAYCTL | AUDIO_CTRL_FLAG_MAINVOL)
#define PCMVOL (PLAYCTL | AUDIO_CTRL_FLAG_PCMVOL)
#define RECVOL (RECCTL | AUDIO_CTRL_FLAG_RECVOL)
#define MONVOL (MONCTL | AUDIO_CTRL_FLAG_MONVOL)
static int
emu10k_get_ac97src(void *arg, uint64_t *valp)
{
ac97_ctrl_t *ctrl = arg;
return (ac97_control_get(ctrl, valp));
}
static int
emu10k_set_ac97src(void *arg, uint64_t value)
{
ac97_ctrl_t *ctrl = arg;
return (ac97_control_set(ctrl, value));
}
static int
emu10k_set_jack3(void *arg, uint64_t value)
{
emu10k_ctrl_t *ec = arg;
emu10k_devc_t *devc = ec->devc;
uint32_t set_val;
uint32_t val;
set_val = ddi_ffs(value & 0xffffffffU);
set_val--;
mutex_enter(&devc->mutex);
switch (set_val) {
case 0:
case 1:
break;
default:
mutex_exit(&devc->mutex);
return (EINVAL);
}
ec->val = value;
/* center/lfe */
if (devc->feature_mask & SB_INVSP) {
set_val = !set_val;
}
if (!devc->suspended) {
if (devc->feature_mask & (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL)) {
val = INL(devc, devc->regs + 0x18);
val &= ~A_IOCFG_GPOUT0;
val |= set_val ? 0x44 : 0x40;
OUTL(devc, val, devc->regs + 0x18);
} else if (devc->feature_mask & SB_LIVE) {
val = INL(devc, devc->regs + HCFG);
val &= ~HCFG_GPOUT0;
val |= set_val ? HCFG_GPOUT0 : 0;
OUTL(devc, val, devc->regs + HCFG);
}
}
mutex_exit(&devc->mutex);
return (0);
}
static int
emu10k_set_recsrc(void *arg, uint64_t value)
{
emu10k_ctrl_t *ec = arg;
emu10k_devc_t *devc = ec->devc;
uint32_t set_val;
set_val = ddi_ffs(value & 0xffffffffU);
set_val--;
/*
* We start assuming well set up AC'97 for stereomix recording.
*/
switch (set_val) {
case INPUT_AC97:
case INPUT_SPD1:
case INPUT_SPD2:
case INPUT_DIGCD:
case INPUT_AUX2:
case INPUT_LINE2:
case INPUT_STEREOMIX:
break;
default:
return (EINVAL);
}
mutex_enter(&devc->mutex);
ec->val = value;
emu10k_write_gpr(devc, GPR_REC_AC97, (set_val == INPUT_AC97));
emu10k_write_gpr(devc, GPR_REC_SPDIF1, (set_val == INPUT_SPD1));
emu10k_write_gpr(devc, GPR_REC_SPDIF2, (set_val == INPUT_SPD2));
emu10k_write_gpr(devc, GPR_REC_DIGCD, (set_val == INPUT_DIGCD));
emu10k_write_gpr(devc, GPR_REC_AUX2, (set_val == INPUT_AUX2));
emu10k_write_gpr(devc, GPR_REC_LINE2, (set_val == INPUT_LINE2));
emu10k_write_gpr(devc, GPR_REC_PCM, (set_val == INPUT_STEREOMIX));
mutex_exit(&devc->mutex);
return (0);
}
static void
emu10k_create_stereo(emu10k_devc_t *devc, int ctl, int gpr,
const char *id, int flags, int defval)
{
emu10k_ctrl_t *ec;
audio_ctrl_desc_t desc;
bzero(&desc, sizeof (desc));
ec = &devc->ctrls[ctl];
ec->devc = devc;
ec->gpr_num = gpr;
desc.acd_name = id;
desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
desc.acd_minvalue = 0;
desc.acd_maxvalue = 100;
desc.acd_flags = flags;
ec->val = (defval << 8) | defval;
ec->ctrl = audio_dev_add_control(devc->adev, &desc,
emu10k_get_control, emu10k_set_stereo, ec);
mutex_enter(&devc->mutex);
emu10k_write_gpr(devc, gpr, emu10k_convert_fixpoint(defval));
emu10k_write_gpr(devc, gpr + 1, emu10k_convert_fixpoint(defval));
mutex_exit(&devc->mutex);
}
static void
emu10k_create_mono(emu10k_devc_t *devc, int ctl, int gpr,
const char *id, int flags, int defval)
{
emu10k_ctrl_t *ec;
audio_ctrl_desc_t desc;
bzero(&desc, sizeof (desc));
ec = &devc->ctrls[ctl];
ec->devc = devc;
ec->gpr_num = gpr;
desc.acd_name = id;
desc.acd_type = AUDIO_CTRL_TYPE_MONO;
desc.acd_minvalue = 0;
desc.acd_maxvalue = 100;
desc.acd_flags = flags;
ec->val = defval;
ec->ctrl = audio_dev_add_control(devc->adev, &desc,
emu10k_get_control, emu10k_set_mono, ec);
mutex_enter(&devc->mutex);
emu10k_write_gpr(devc, gpr, emu10k_convert_fixpoint(defval));
mutex_exit(&devc->mutex);
}
/*
* AC'97 source. The AC'97 PCM record channel is routed to our
* mixer. While we could support the direct monitoring capability of
* the AC'97 part itself, this would not work correctly with outputs
* that are not routed via AC'97 (such as the Live Drive headphones
* or digital outputs.) So we just offer the ability to select one
* AC'97 source, and then offer independent ability to either monitor
* or record from the AC'97 mixer's PCM record channel.
*/
static void
emu10k_create_ac97src(emu10k_devc_t *devc)
{
emu10k_ctrl_t *ec;
audio_ctrl_desc_t desc;
ac97_ctrl_t *ac;
const audio_ctrl_desc_t *acd;
bzero(&desc, sizeof (desc));
ec = &devc->ctrls[CTL_AC97SRC];
desc.acd_name = "ac97-source";
desc.acd_type = AUDIO_CTRL_TYPE_ENUM;
desc.acd_flags = RECCTL;
ec->devc = devc;
ac = ac97_control_find(devc->ac97, AUDIO_CTRL_ID_RECSRC);
if (ac == NULL) {
return;
}
acd = ac97_control_desc(ac);
for (int i = 0; i < 64; i++) {
const char *n;
if (((acd->acd_minvalue & (1ULL << i)) == 0) ||
((n = acd->acd_enum[i]) == NULL)) {
continue;
}
desc.acd_enum[i] = acd->acd_enum[i];
/* we suppress some port options */
if ((strcmp(n, AUDIO_PORT_STEREOMIX) == 0) ||
(strcmp(n, AUDIO_PORT_MONOMIX) == 0) ||
(strcmp(n, AUDIO_PORT_VIDEO) == 0)) {
continue;
}
desc.acd_minvalue |= (1ULL << i);
desc.acd_maxvalue |= (1ULL << i);
}
ec->ctrl = audio_dev_add_control(devc->adev, &desc,
emu10k_get_ac97src, emu10k_set_ac97src, ac);
}
/*
* Record source... this one is tricky. While the chip will
* conceivably let us *mix* some of the audio streams for recording,
* the AC'97 inputs don't have this capability. Offering it to users
* is likely to be confusing, so we offer a single record source
* selection option. Its not ideal, but it ought to be good enough
* for the vast majority of users.
*/
static void
emu10k_create_recsrc(emu10k_devc_t *devc)
{
emu10k_ctrl_t *ec;
audio_ctrl_desc_t desc;
ac97_ctrl_t *ac;
bzero(&desc, sizeof (desc));
ec = &devc->ctrls[CTL_RECSRC];
desc.acd_name = AUDIO_CTRL_ID_RECSRC;
desc.acd_type = AUDIO_CTRL_TYPE_ENUM;
desc.acd_flags = RECCTL;
desc.acd_minvalue = 0;
desc.acd_maxvalue = 0;
bzero(desc.acd_enum, sizeof (desc.acd_enum));
ec->devc = devc;
ac = ac97_control_find(devc->ac97, AUDIO_CTRL_ID_RECSRC);
/* only low order bits set by AC'97 */
ASSERT(desc.acd_minvalue == desc.acd_maxvalue);
ASSERT((desc.acd_minvalue & ~0xffff) == 0);
/*
* It would be really cool if we could detect whether these
* options are all sensible on a given configuration. Units
* without live-drive support, and units without a physical
* live-drive, simply can't do all these.
*/
if (ac != NULL) {
desc.acd_minvalue |= (1 << INPUT_AC97);
desc.acd_maxvalue |= (1 << INPUT_AC97);
desc.acd_enum[INPUT_AC97] = "ac97";
ec->val = (1 << INPUT_AC97);
} else {
/* next best guess */
ec->val = (1 << INPUT_LINE2);
}
desc.acd_minvalue |= (1 << INPUT_SPD1);
desc.acd_maxvalue |= (1 << INPUT_SPD1);
desc.acd_enum[INPUT_SPD1] = AUDIO_PORT_SPDIFIN;
desc.acd_minvalue |= (1 << INPUT_SPD2);
desc.acd_maxvalue |= (1 << INPUT_SPD2);
desc.acd_enum[INPUT_SPD2] = "spdif2-in";
desc.acd_minvalue |= (1 << INPUT_DIGCD);
desc.acd_maxvalue |= (1 << INPUT_DIGCD);
desc.acd_enum[INPUT_DIGCD] = "digital-cd";
desc.acd_minvalue |= (1 << INPUT_AUX2);
desc.acd_maxvalue |= (1 << INPUT_AUX2);
desc.acd_enum[INPUT_AUX2] = AUDIO_PORT_AUX2IN;
desc.acd_minvalue |= (1 << INPUT_LINE2);
desc.acd_maxvalue |= (1 << INPUT_LINE2);
desc.acd_enum[INPUT_LINE2] = "line2-in";
desc.acd_minvalue |= (1 << INPUT_STEREOMIX);
desc.acd_maxvalue |= (1 << INPUT_STEREOMIX);
desc.acd_enum[INPUT_STEREOMIX] = AUDIO_PORT_STEREOMIX;
emu10k_write_gpr(devc, GPR_REC_SPDIF1, 0);
emu10k_write_gpr(devc, GPR_REC_SPDIF2, 0);
emu10k_write_gpr(devc, GPR_REC_DIGCD, 0);
emu10k_write_gpr(devc, GPR_REC_AUX2, 0);
emu10k_write_gpr(devc, GPR_REC_LINE2, 0);
emu10k_write_gpr(devc, GPR_REC_PCM, 0);
emu10k_write_gpr(devc, GPR_REC_AC97, 1);
ec->ctrl = audio_dev_add_control(devc->adev, &desc,
emu10k_get_control, emu10k_set_recsrc, ec);
}
static void
emu10k_create_jack3(emu10k_devc_t *devc)
{
emu10k_ctrl_t *ec;
audio_ctrl_desc_t desc;
bzero(&desc, sizeof (desc));
ec = &devc->ctrls[CTL_JACK3];
desc.acd_name = AUDIO_CTRL_ID_JACK3;
desc.acd_type = AUDIO_CTRL_TYPE_ENUM;
desc.acd_flags = AUDIO_CTRL_FLAG_RW;
desc.acd_minvalue = 0x3;
desc.acd_maxvalue = 0x3;
bzero(desc.acd_enum, sizeof (desc.acd_enum));
ec->devc = devc;
ec->val = 0x1;
desc.acd_enum[0] = AUDIO_PORT_CENLFE;
desc.acd_enum[1] = AUDIO_PORT_SPDIFOUT;
ec->ctrl = audio_dev_add_control(devc->adev, &desc,
emu10k_get_control, emu10k_set_jack3, ec);
}
static void
emu10k_create_controls(emu10k_devc_t *devc)
{
ac97_t *ac97;
ac97_ctrl_t *ac;
emu10k_create_mono(devc, CTL_VOLUME, GPR_VOL_PCM,
AUDIO_CTRL_ID_VOLUME, PCMVOL, 75);
emu10k_create_stereo(devc, CTL_FRONT, GPR_VOL_FRONT,
AUDIO_CTRL_ID_FRONT, MAINVOL, 100);
emu10k_create_stereo(devc, CTL_SURROUND, GPR_VOL_SURR,
AUDIO_CTRL_ID_SURROUND, MAINVOL, 100);
if (devc->feature_mask & (SB_51 | SB_71)) {
emu10k_create_mono(devc, CTL_CENTER, GPR_VOL_CEN,
AUDIO_CTRL_ID_CENTER, MAINVOL, 100);
emu10k_create_mono(devc, CTL_LFE, GPR_VOL_LFE,
AUDIO_CTRL_ID_LFE, MAINVOL, 100);
}
if (devc->feature_mask & SB_71) {
emu10k_create_stereo(devc, CTL_SIDE, GPR_VOL_SIDE,
"side", MAINVOL, 100);
}
emu10k_create_stereo(devc, CTL_RECGAIN, GPR_VOL_REC,
AUDIO_CTRL_ID_RECGAIN, RECVOL, 50);
emu10k_create_ac97src(devc);
emu10k_create_recsrc(devc);
/*
* 5.1 devices have versa jack. Note that from what we can
* tell, none of the 7.1 devices have or need this versa jack,
* as they all seem to have a dedicated digital I/O port.
*/
if ((devc->feature_mask & SB_51) &&
!(devc->feature_mask & SB_AUDIGY2VAL)) {
emu10k_create_jack3(devc);
}
/* these ones AC'97 can manage directly */
ac97 = devc->ac97;
if ((ac = ac97_control_find(ac97, AUDIO_CTRL_ID_MICBOOST)) != NULL)
ac97_control_register(ac);
if ((ac = ac97_control_find(ac97, AUDIO_CTRL_ID_MICGAIN)) != NULL)
ac97_control_register(ac);
/* set any AC'97 analog outputs to full volume (no attenuation) */
if ((ac = ac97_control_find(ac97, AUDIO_CTRL_ID_FRONT)) != NULL)
(void) ac97_control_set(ac, (100 << 8) | 100);
if ((ac = ac97_control_find(ac97, AUDIO_CTRL_ID_LINEOUT)) != NULL)
(void) ac97_control_set(ac, (100 << 8) | 100);
if ((ac = ac97_control_find(ac97, AUDIO_CTRL_ID_SURROUND)) != NULL)
(void) ac97_control_set(ac, (100 << 8) | 100);
if ((ac = ac97_control_find(ac97, AUDIO_CTRL_ID_CENTER)) != NULL)
(void) ac97_control_set(ac, 100);
if ((ac = ac97_control_find(ac97, AUDIO_CTRL_ID_LFE)) != NULL)
(void) ac97_control_set(ac, 100);
/* Monitor sources */
emu10k_create_stereo(devc, CTL_AC97, GPR_MON_AC97,
"ac97-monitor", MONVOL, 0);
emu10k_create_stereo(devc, CTL_SPD1, GPR_MON_SPDIF1,
AUDIO_PORT_SPDIFIN, MONVOL, 0);
emu10k_create_stereo(devc, CTL_DIGCD, GPR_MON_DIGCD,
"digital-cd", MONVOL, 0);
emu10k_create_stereo(devc, CTL_SPD1, GPR_MON_SPDIF1,
AUDIO_PORT_SPDIFIN, MONVOL, 0);
if ((devc->feature_mask & SB_NOEXP) == 0) {
/*
* These ports are only available via an external
* expansion box. Don't expose them for cards that
* don't have support for it.
*/
emu10k_create_stereo(devc, CTL_HEADPH, GPR_VOL_HEADPH,
AUDIO_CTRL_ID_HEADPHONE, MAINVOL, 100);
emu10k_create_stereo(devc, CTL_SPD2, GPR_MON_SPDIF2,
"spdif2-in", MONVOL, 0);
emu10k_create_stereo(devc, CTL_LINE2, GPR_MON_LINE2,
"line2-in", MONVOL, 0);
emu10k_create_stereo(devc, CTL_AUX2, GPR_MON_AUX2,
AUDIO_PORT_AUX2IN, MONVOL, 0);
}
}
static void
emu10k_load_dsp(emu10k_devc_t *devc, uint32_t *code, int ncode,
uint32_t *init, int ninit)
{
int i;
if (ncode > 1024) {
audio_dev_warn(devc->adev, "DSP file size too big");
return;
}
if (ninit > MAX_GPR) {
audio_dev_warn(devc->adev, "Too many inits");
return;
}
/* Upload our DSP code */
for (i = 0; i < ncode; i++) {
emu10k_write_efx(devc, UC0 + i, code[i]);
}
/* Upload the initialization settings */
for (i = 0; i < ninit; i += 2) {
emu10k_write_reg(devc, init[i] + GPR0, 0, init[i + 1]);
}
}
#define LIVE_NOP() \
emu10k_write_efx(devc, UC0 + (pc * 2), 0x10040); \
emu10k_write_efx(devc, UC0 + (pc * 2 + 1), 0x610040); \
pc++
#define LIVE_ACC3(r, a, x, y) /* z=w+x+y */ \
emu10k_write_efx(devc, UC0 + (pc * 2), (x << 10) | y); \
emu10k_write_efx(devc, UC0 + (pc * 2 + 1), (6 << 20) | (r << 10) | a); \
pc++
#define AUDIGY_ACC3(r, a, x, y) /* z=w+x+y */ \
emu10k_write_efx(devc, UC0 + (pc * 2), (x << 12) | y); \
emu10k_write_efx(devc, UC0 + (pc * 2+1), (6 << 24) | (r << 12) | a); \
pc++
#define AUDIGY_NOP() AUDIGY_ACC3(0xc0, 0xc0, 0xc0, 0xc0)
static void
emu10k_init_effects(emu10k_devc_t *devc)
{
int i;
unsigned short pc;
ASSERT(mutex_owned(&devc->mutex));
if (devc->feature_mask & (SB_AUDIGY|SB_AUDIGY2|SB_AUDIGY2VAL)) {
pc = 0;
for (i = 0; i < 512; i++) {
AUDIGY_NOP();
}
for (i = 0; i < 256; i++)
emu10k_write_efx(devc, GPR0 + i, 0);
emu10k_write_reg(devc, AUDIGY_DBG, 0, 0);
emu10k_load_dsp(devc,
emu10k2_code,
sizeof (emu10k2_code) / sizeof (emu10k2_code[0]),
emu10k2_init,
sizeof (emu10k2_init) / sizeof (emu10k2_init[0]));
} else {
pc = 0;
for (i = 0; i < 512; i++) {
LIVE_NOP();
}
for (i = 0; i < 256; i++)
emu10k_write_efx(devc, GPR0 + i, 0);
emu10k_write_reg(devc, DBG, 0, 0);
emu10k_load_dsp(devc,
emu10k1_code,
sizeof (emu10k1_code) / sizeof (emu10k1_code[0]),
emu10k1_init,
sizeof (emu10k1_init) / sizeof (emu10k1_init[0]));
}
}
/* mixer */
static struct {
uint16_t devid;
uint16_t subid;
const char *model;
const char *prod;
unsigned feature_mask;
} emu10k_cards[] = {
{ 0x2, 0x0020, "CT4670", "Live! Value", SB_LIVE | SB_NOEXP },
{ 0x2, 0x0021, "CT4621", "Live!", SB_LIVE },
{ 0x2, 0x100a, "SB0220", "Live! 5.1 Digital",
SB_LIVE | SB_51 | SB_NOEXP },
{ 0x2, 0x8022, "CT4780", "Live! Value", SB_LIVE },
{ 0x2, 0x8023, "CT4790", "PCI512", SB_LIVE | SB_NOEXP },
{ 0x2, 0x8026, "CT4830", "Live! Value", SB_LIVE },
{ 0x2, 0x8028, "CT4870", "Live! Value", SB_LIVE },
{ 0x2, 0x8031, "CT4831", "Live! Value", SB_LIVE },
{ 0x2, 0x8040, "CT4760", "Live!", SB_LIVE },
{ 0x2, 0x8051, "CT4850", "Live! Value", SB_LIVE },
{ 0x2, 0x8061, "SB0060", "Live! 5.1", SB_LIVE | SB_51 },
{ 0x2, 0x8064, "SB0100", "Live! 5.1", SB_LIVE | SB_51 },
{ 0x2, 0x8065, "SB0220", "Live! 5.1", SB_LIVE | SB_51 },
{ 0x2, 0x8066, "SB0228", "Live! 5.1", SB_LIVE | SB_51 },
{ 0x4, 0x0051, "SB0090", "Audigy", SB_AUDIGY | SB_51 },
{ 0x4, 0x0052, "SB0160", "Audigy ES", SB_AUDIGY | SB_51 },
{ 0x4, 0x0053, "SB0092", "Audigy", SB_AUDIGY | SB_51 },
{ 0x4, 0x1002, "SB0240P", "Audigy 2 Platinum",
SB_AUDIGY2 | SB_71 | SB_INVSP },
{ 0x4, 0x1003, "SB0353", "Audigy 2 ZS", SB_AUDIGY2 | SB_71 | SB_INVSP },
{ 0x4, 0x1005, "SB0280", "Audigy 2 Platinum EX", SB_AUDIGY2 | SB_71 },
{ 0x4, 0x1007, "SB0240", "Audigy 2", SB_AUDIGY2 | SB_71 },
{ 0x4, 0x2001, "SB0360", "Audigy 2 ZS", SB_AUDIGY2 | SB_71 | SB_INVSP },
{ 0x4, 0x2002, "SB0350", "Audigy 2 ZS", SB_AUDIGY2 | SB_71 | SB_INVSP },
{ 0x4, 0x2006, "SB0350", "Audigy 2", SB_AUDIGY2 | SB_71 | SB_INVSP },
{ 0x4, 0x2007, "SB0380", "Audigy 4 Pro", SB_AUDIGY2 | SB_71 },
{ 0x8, 0x1001, "SB0400", "Audigy 2 Value",
SB_AUDIGY2VAL | SB_71 | SB_NOEXP },
{ 0x8, 0x1021, "SB0610", "Audigy 4",
SB_AUDIGY2VAL | SB_71 | SB_NOEXP },
{ 0x8, 0x2001, "SB0530", "Audigy 2 ZS Notebook",
SB_AUDIGY2VAL | SB_71 },
{ 0, 0, NULL, NULL, 0 },
};
int
emu10k_attach(dev_info_t *dip)
{
uint16_t pci_command;
uint16_t subid;
uint16_t devid;
emu10k_devc_t *devc;
ddi_acc_handle_t pcih;
ddi_dma_cookie_t cookie;
uint_t count;
ulong_t len;
int i;
const char *name;
const char *model;
char namebuf[64];
int feature_mask;
devc = kmem_zalloc(sizeof (*devc), KM_SLEEP);
devc->dip = dip;
ddi_set_driver_private(dip, devc);
if ((devc->adev = audio_dev_alloc(dip, 0)) == NULL) {
cmn_err(CE_WARN, "audio_dev_alloc failed");
goto error;
}
if (pci_config_setup(dip, &pcih) != DDI_SUCCESS) {
audio_dev_warn(devc->adev, "pci_config_setup failed");
goto error;
}
devc->pcih = pcih;
devid = pci_config_get16(pcih, PCI_CONF_DEVID);
subid = pci_config_get16(pcih, PCI_CONF_SUBSYSID);
pci_command = pci_config_get16(pcih, PCI_CONF_COMM);
pci_command |= PCI_COMM_ME | PCI_COMM_IO;
pci_config_put16(pcih, PCI_CONF_COMM, pci_command);
if ((ddi_regs_map_setup(dip, 1, &devc->regs, 0, 0, &dev_attr,
&devc->regsh)) != DDI_SUCCESS) {
audio_dev_warn(devc->adev, "failed to map registers");
goto error;
}
switch (devid) {
case PCI_DEVICE_ID_SBLIVE:
name = "Live!";
model = "CT????";
feature_mask = SB_LIVE;
break;
case PCI_DEVICE_ID_AUDIGYVALUE:
name = "Audigy 2 Value";
model = "SB????";
feature_mask = SB_AUDIGY2VAL;
break;
case PCI_DEVICE_ID_AUDIGY:
if (subid >= 0x1002 && subid <= 0x2005) {
name = "Audigy 2";
model = "SB????";
feature_mask = SB_AUDIGY2;
} else {
name = "Audigy";
model = "SB????";
feature_mask = SB_AUDIGY;
}
break;
default:
audio_dev_warn(devc->adev, "Unrecognized device");
goto error;
}
for (i = 0; emu10k_cards[i].prod; i++) {
if ((devid == emu10k_cards[i].devid) &&
(subid == emu10k_cards[i].subid)) {
name = emu10k_cards[i].prod;
model = emu10k_cards[i].model;
feature_mask = emu10k_cards[i].feature_mask;
break;
}
}
devc->feature_mask = feature_mask;
(void) snprintf(namebuf, sizeof (namebuf), "Sound Blaster %s", name);
audio_dev_set_description(devc->adev, namebuf);
audio_dev_set_version(devc->adev, model);
if (emu10k_setup_intrs(devc) != DDI_SUCCESS)
goto error;
/* allocate static page table memory */
devc->max_mem = AUDIO_MEMSIZE;
/* SB Live/Audigy supports at most 32M of memory) */
if (devc->max_mem > 32 * 1024 * 1024)
devc->max_mem = 32 * 1024 * 1024;
devc->max_pages = devc->max_mem / 4096;
if (devc->max_pages < 1024)
devc->max_pages = 1024;
/* Allocate page table */
if (ddi_dma_alloc_handle(devc->dip, &dma_attr_buf, DDI_DMA_SLEEP, NULL,
&devc->pt_dmah) != DDI_SUCCESS) {
audio_dev_warn(devc->adev,
"failed to allocate page table handle");
return (DDI_FAILURE);
}
if (ddi_dma_mem_alloc(devc->pt_dmah, devc->max_pages * 4,
&dev_attr, DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL,
&devc->pt_kaddr, &len, &devc->pt_acch) !=
DDI_SUCCESS) {
audio_dev_warn(devc->adev,
"failed to allocate memory for page table");
return (DDI_FAILURE);
}
if (ddi_dma_addr_bind_handle(devc->pt_dmah, NULL,
devc->pt_kaddr, len, DDI_DMA_CONSISTENT | DDI_DMA_WRITE,
DDI_DMA_SLEEP, NULL, &cookie, &count) != DDI_SUCCESS) {
audio_dev_warn(devc->adev,
"failed binding page table DMA handle");
return (DDI_FAILURE);
}
devc->page_map = (void *)devc->pt_kaddr;
devc->pt_paddr = cookie.dmac_address;
bzero(devc->pt_kaddr, devc->max_pages * 4);
/* Allocate silent page */
if (ddi_dma_alloc_handle(devc->dip, &dma_attr_buf, DDI_DMA_SLEEP, NULL,
&devc->silence_dmah) != DDI_SUCCESS) {
audio_dev_warn(devc->adev,
"failed to allocate silent page handle");
return (DDI_FAILURE);
}
if (ddi_dma_mem_alloc(devc->silence_dmah, 4096,
&buf_attr, DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL,
&devc->silence_kaddr, &len,
&devc->silence_acch) != DDI_SUCCESS) {
audio_dev_warn(devc->adev,
"failed to allocate silent page memory");
return (DDI_FAILURE);
}
(void) ddi_dma_sync(devc->silence_dmah, 0, 0, DDI_DMA_SYNC_FORDEV);
if (ddi_dma_addr_bind_handle(devc->silence_dmah, NULL,
devc->silence_kaddr, len, DDI_DMA_CONSISTENT | DDI_DMA_WRITE,
DDI_DMA_SLEEP, NULL, &cookie, &count) != DDI_SUCCESS) {
audio_dev_warn(devc->adev,
"failed binding silent page DMA handle");
return (DDI_FAILURE);
}
devc->silence_paddr = cookie.dmac_address;
bzero(devc->silence_kaddr, 4096);
devc->audio_memptr = 4096; /* Skip the silence page */
for (i = 0; i < devc->max_pages; i++)
FILL_PAGE_MAP_ENTRY(i, devc->silence_paddr);
(void) ddi_dma_sync(devc->pt_dmah, 0, 0, DDI_DMA_SYNC_FORDEV);
devc->ac97 = ac97_allocate(devc->adev, dip,
emu10k_read_ac97, emu10k_write_ac97, devc);
if (devc->ac97 == NULL) {
audio_dev_warn(devc->adev, "failed to allocate ac97 handle");
goto error;
}
ac97_probe_controls(devc->ac97);
/* allocate voice 0 for play */
if (emu10k_alloc_port(devc, EMU10K_REC) != DDI_SUCCESS)
goto error;
if (emu10k_alloc_port(devc, EMU10K_PLAY) != DDI_SUCCESS)
goto error;
/* now initialize the hardware */
mutex_enter(&devc->mutex);
if (emu10k_hwinit(devc) != DDI_SUCCESS) {
mutex_exit(&devc->mutex);
goto error;
}
mutex_exit(&devc->mutex);
emu10k_create_controls(devc);
/* set up kernel statistics */
if ((devc->ksp = kstat_create(EMU10K_NAME, ddi_get_instance(dip),
EMU10K_NAME, "controller", KSTAT_TYPE_INTR,
1, KSTAT_FLAG_PERSISTENT)) != NULL) {
kstat_install(devc->ksp);
}
if (audio_dev_register(devc->adev) != DDI_SUCCESS) {
audio_dev_warn(devc->adev, "unable to register audio device");
goto error;
}
(void) ddi_intr_enable(devc->ih);
ddi_report_dev(dip);
return (DDI_SUCCESS);
error:
emu10k_destroy(devc);
return (DDI_FAILURE);
}
int
emu10k_resume(dev_info_t *dip)
{
emu10k_devc_t *devc;
emu10k_portc_t *portc;
devc = ddi_get_driver_private(dip);
for (int i = 0; i < EMU10K_NUM_PORTC; i++) {
portc = devc->portc[i];
audio_engine_reset(portc->engine);
}
mutex_enter(&devc->mutex);
if (emu10k_hwinit(devc) != DDI_SUCCESS) {
mutex_exit(&devc->mutex);
/*
* In case of failure, we leave the chip suspended,
* but don't panic. Audio service is not normally a a
* critical service.
*/
audio_dev_warn(devc->adev, "FAILED to RESUME device");
return (DDI_SUCCESS);
}
emu10k_refresh_mixer(devc);
devc->suspended = B_FALSE;
for (int i = 0; i < EMU10K_NUM_PORTC; i++) {
portc = devc->portc[i];
portc->stop_port(portc);
portc->dopos = 1;
if (portc->started) {
portc->reset_port(portc);
portc->start_port(portc);
}
}
mutex_exit(&devc->mutex);
/* resume ac97 */
ac97_resume(devc->ac97);
return (DDI_SUCCESS);
}
int
emu10k_detach(emu10k_devc_t *devc)
{
if (audio_dev_unregister(devc->adev) != DDI_SUCCESS)
return (DDI_FAILURE);
emu10k_destroy(devc);
return (DDI_SUCCESS);
}
int
emu10k_suspend(emu10k_devc_t *devc)
{
ac97_suspend(devc->ac97);
mutex_enter(&devc->mutex);
devc->suspended = B_TRUE;
emu10k_write_reg(devc, CLIEL, 0, 0);
emu10k_write_reg(devc, CLIEH, 0, 0);
if (!(devc->feature_mask & SB_LIVE)) {
emu10k_write_reg(devc, HLIEL, 0, 0x0);
emu10k_write_reg(devc, HLIEH, 0, 0x0);
}
OUTL(devc, 0, devc->regs + IE); /* Intr enable (all off) */
for (int i = 0; i < EMU10K_NUM_PORTC; i++) {
emu10k_portc_t *portc = devc->portc[i];
portc->stop_port(portc);
}
/* stop all voices */
for (int i = 0; i < 64; i++) {
emu10k_write_reg(devc, VEDS, i, 0);
}
for (int i = 0; i < 64; i++) {
emu10k_write_reg(devc, VTFT, i, 0);
emu10k_write_reg(devc, CVCF, i, 0);
emu10k_write_reg(devc, PTAB, i, 0);
emu10k_write_reg(devc, CPF, i, 0);
}
/*
* Turn off the hardware
*/
OUTL(devc,
HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK |
HCFG_MUTEBUTTONENABLE, devc->regs + HCFG);
/* stop ADC recording */
emu10k_write_reg(devc, ADCSR, 0, 0x0);
emu10k_write_reg(devc, ADCBA, 0, 0x0);
emu10k_write_reg(devc, ADCBA, 0, 0x0);
emu10k_write_reg(devc, PTBA, 0, 0);
mutex_exit(&devc->mutex);
return (DDI_SUCCESS);
}
static int emu10k_ddi_attach(dev_info_t *, ddi_attach_cmd_t);
static int emu10k_ddi_detach(dev_info_t *, ddi_detach_cmd_t);
static int emu10k_ddi_quiesce(dev_info_t *);
static struct dev_ops emu10k_dev_ops = {
DEVO_REV, /* rev */
0, /* refcnt */
NULL, /* getinfo */
nulldev, /* identify */
nulldev, /* probe */
emu10k_ddi_attach, /* attach */
emu10k_ddi_detach, /* detach */
nodev, /* reset */
NULL, /* cb_ops */
NULL, /* bus_ops */
NULL, /* power */
emu10k_ddi_quiesce, /* quiesce */
};
static struct modldrv emu10k_modldrv = {
&mod_driverops, /* drv_modops */
"Creative EMU10K Audio", /* linkinfo */
&emu10k_dev_ops, /* dev_ops */
};
static struct modlinkage modlinkage = {
MODREV_1,
{ &emu10k_modldrv, NULL }
};
int
_init(void)
{
int rv;
audio_init_ops(&emu10k_dev_ops, EMU10K_NAME);
if ((rv = mod_install(&modlinkage)) != 0) {
audio_fini_ops(&emu10k_dev_ops);
}
return (rv);
}
int
_fini(void)
{
int rv;
if ((rv = mod_remove(&modlinkage)) == 0) {
audio_fini_ops(&emu10k_dev_ops);
}
return (rv);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
int
emu10k_ddi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
switch (cmd) {
case DDI_ATTACH:
return (emu10k_attach(dip));
case DDI_RESUME:
return (emu10k_resume(dip));
default:
return (DDI_FAILURE);
}
}
int
emu10k_ddi_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
emu10k_devc_t *devc;
devc = ddi_get_driver_private(dip);
switch (cmd) {
case DDI_DETACH:
return (emu10k_detach(devc));
case DDI_SUSPEND:
return (emu10k_suspend(devc));
default:
return (DDI_FAILURE);
}
}
int
emu10k_ddi_quiesce(dev_info_t *dip)
{
emu10k_devc_t *devc;
devc = ddi_get_driver_private(dip);
/* stop all voices */
for (int i = 0; i < 64; i++) {
emu10k_write_reg(devc, VEDS, i, 0);
}
for (int i = 0; i < 64; i++) {
emu10k_write_reg(devc, VTFT, i, 0);
emu10k_write_reg(devc, CVCF, i, 0);
emu10k_write_reg(devc, PTAB, i, 0);
emu10k_write_reg(devc, CPF, i, 0);
}
/*
* Turn off the hardware
*/
OUTL(devc, 0, devc->regs + IE); /* Intr enable (all off) */
OUTL(devc,
HCFG_LOCKSOUNDCACHE | HCFG_LOCKTANKCACHE_MASK |
HCFG_MUTEBUTTONENABLE, devc->regs + HCFG);
/* stop ADC recording */
emu10k_write_reg(devc, ADCSR, 0, 0x0);
emu10k_write_reg(devc, ADCBA, 0, 0x0);
emu10k_write_reg(devc, ADCBA, 0, 0x0);
emu10k_write_reg(devc, PTBA, 0, 0);
return (DDI_SUCCESS);
}