audioens.c revision 505c7a699305ccafcfecc1ab0e7d4a25e2bfd1c2
/*
* 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: Creative/Ensoniq AudioPCI97 driver (ES1371/ES1373)
*
* This driver is used with the original Ensoniq AudioPCI97 card and many
* PCI based Sound Blaster cards by Creative Technologies. For example
* Sound Blaster PCI128 and Creative/Ectiva EV1938.
*/
/*
* This file is part of Open Sound System
*
* Copyright (C) 4Front Technologies 1996-2008.
*
* This software is released under CDDL 1.0 source license.
* See the COPYING file included in the main directory of this source
* distribution for the license terms and conditions.
*/
#include <sys/audio/audio_driver.h>
#include <sys/audio/ac97.h>
#include <sys/note.h>
#include <sys/pci.h>
#include "audioens.h"
/*
* The original OSS driver used a single duplex engine and a separate
* playback only engine. Instead, we expose three engines, one for input
* and two for output.
*/
/*
* Set the latency to 32, 64, 96, 128 clocks - some APCI97 devices exhibit
* garbled audio in some cases and setting the latency to higer values fixes it
* Values: 32, 64, 96, 128 - Default: 64 (or defined by bios)
*/
int audioens_latency = 0;
/*
* Enable SPDIF port on SoundBlaster 128D or Sound Blaster Digital-4.1 models
* Values: 1=Enable 0=Disable Default: 0
*/
int audioens_spdif = 0;
/*
* Note: Latest devices can support SPDIF with AC3 pass thru.
* However, in order to do this, one of the two DMA engines must be
* dedicated to this, which would prevent the card from supporting 4
* channel audio. For now we don't bother with the AC3 pass through
* mode, and instead just focus on 4 channel support. In the future,
* this could be selectable via a property.
*/
#define ENSONIQ_VENDOR_ID 0x1274
#define CREATIVE_VENDOR_ID 0x1102
#define ECTIVA_VENDOR_ID 0x1102
#define ENSONIQ_ES1371 0x1371
#define ENSONIQ_ES5880 0x8001
#define ENSONIQ_ES5880A 0x8002
#define ENSONIQ_ES5880B 0x5880
#define ECTIVA_ES1938 0x8938
#define DEFRATE 48000
#define DEFINTS 75
#define DRVNAME "audioens"
typedef struct audioens_port
{
/* Audio parameters */
boolean_t trigger;
boolean_t suspended;
int speed;
int num;
#define PORT_DAC 0
#define PORT_ADC 1
#define PORT_MAX PORT_ADC
caddr_t kaddr;
uint32_t paddr;
ddi_acc_handle_t acch;
ddi_dma_handle_t dmah;
int nchan;
unsigned fragfr;
unsigned nfrags;
unsigned nframes;
unsigned frameno;
uint64_t count;
struct audioens_dev *dev;
audio_engine_t *engine;
} audioens_port_t;
typedef struct audioens_dev
{
audio_dev_t *osdev;
kmutex_t mutex;
uint16_t devid;
uint8_t revision;
dev_info_t *dip;
boolean_t enabled;
int pintrs;
int rintrs;
kstat_t *ksp;
audioens_port_t port[PORT_MAX + 1];
ac97_t *ac97;
caddr_t regs;
ddi_acc_handle_t acch;
ddi_intr_handle_t ihandle[1];
} audioens_dev_t;
static ddi_device_acc_attr_t acc_attr = {
DDI_DEVICE_ATTR_V0,
DDI_STRUCTURE_LE_ACC,
DDI_STRICTORDER_ACC
};
static ddi_device_acc_attr_t buf_attr = {
DDI_DEVICE_ATTR_V0,
DDI_NEVERSWAP_ACC,
DDI_STRICTORDER_ACC
};
/*
* The hardware appears to be able to address up to 16-bits worth of longwords,
* giving a total address space of 256K. Note, however, that we will restrict
* this further when we do fragment and memory allocation. At its very highest
* clock rate (48 kHz) and sample size (16-bit stereo), and lowest interrupt
* rate (32 Hz), we only need 6000 bytes per fragment.
*
* So with an allocated buffer size of 64K, we can support at least 10 frags,
* which is more than enough. (The legacy Sun driver used only 2 fragments.)
*/
#define AUDIOENS_BUF_LEN (65536)
static ddi_dma_attr_t dma_attr = {
DMA_ATTR_VERSION, /* dma_attr_version */
0x0, /* dma_attr_addr_lo */
0xffffffffU, /* dma_attr_addr_hi */
0x3ffff, /* dma_attr_count_max */
0x8, /* dma_attr_align */
0x7f, /* dma_attr_burstsizes */
0x1, /* dma_attr_minxfer */
0x3ffff, /* dma_attr_maxxfer */
0x3ffff, /* dma_attr_seg */
0x1, /* dma_attr_sgllen */
0x1, /* dma_attr_granular */
0 /* dma_attr_flags */
};
#define GET8(dev, offset) \
ddi_get8(dev->acch, (uint8_t *)(dev->regs + (offset)))
#define GET16(dev, offset) \
ddi_get16(dev->acch, (uint16_t *)(void *)(dev->regs + (offset)))
#define GET32(dev, offset) \
ddi_get32(dev->acch, (uint32_t *)(void *)(dev->regs + (offset)))
#define PUT8(dev, offset, v) \
ddi_put8(dev->acch, (uint8_t *)(dev->regs + (offset)), v)
#define PUT16(dev, offset, v) \
ddi_put16(dev->acch, (uint16_t *)(void *)(dev->regs + (offset)), v)
#define PUT32(dev, offset, v) \
ddi_put32(dev->acch, (uint32_t *)(void *)(dev->regs + (offset)), v)
#define CLR8(dev, offset, v) PUT8(dev, offset, GET8(dev, offset) & ~(v))
#define SET8(dev, offset, v) PUT8(dev, offset, GET8(dev, offset) | (v))
#define CLR32(dev, offset, v) PUT32(dev, offset, GET32(dev, offset) & ~(v))
#define SET32(dev, offset, v) PUT32(dev, offset, GET32(dev, offset) | (v))
#define KSINTR(dev) ((kstat_intr_t *)((dev)->ksp->ks_data))
static void audioens_init_hw(audioens_dev_t *);
static void audioens_init_port(audioens_port_t *);
static void audioens_start_port(audioens_port_t *);
static void audioens_stop_port(audioens_port_t *);
static void audioens_update_port(audioens_port_t *);
static uint16_t
audioens_rd97(void *dev_, uint8_t wAddr)
{
audioens_dev_t *dev = dev_;
int i, dtemp;
mutex_enter(&dev->mutex);
dtemp = GET32(dev, CONC_dCODECCTL_OFF);
/* wait for WIP to go away saving the current state for later */
for (i = 0; i < 0x100UL; ++i) {
dtemp = GET32(dev, CONC_dCODECCTL_OFF);
if ((dtemp & (1UL << 30)) == 0)
break;
}
/* write addr w/data=0 and assert read request ... */
PUT32(dev, CONC_dCODECCTL_OFF, ((int)wAddr << 16) | (1UL << 23));
/* now wait for the data (RDY) */
for (i = 0; i < 0x100UL; ++i) {
dtemp = GET32(dev, CONC_dCODECCTL_OFF);
if (dtemp & (1UL << 31))
break;
}
dtemp = GET32(dev, CONC_dCODECCTL_OFF);
mutex_exit(&dev->mutex);
return (dtemp & 0xffff);
}
static void
audioens_wr97(void *dev_, uint8_t wAddr, uint16_t wData)
{
audioens_dev_t *dev = dev_;
int i, dtemp;
mutex_enter(&dev->mutex);
/* wait for WIP to go away */
for (i = 0; i < 0x100UL; ++i) {
dtemp = GET32(dev, CONC_dCODECCTL_OFF);
if ((dtemp & (1UL << 30)) == 0)
break;
}
PUT32(dev, CONC_dCODECCTL_OFF, ((int)wAddr << 16) | wData);
mutex_exit(&dev->mutex);
}
static unsigned short
SRCRegRead(audioens_dev_t *dev, unsigned short reg)
{
int i, dtemp;
dtemp = GET32(dev, CONC_dSRCIO_OFF);
/* wait for ready */
for (i = 0; i < SRC_IOPOLL_COUNT; ++i) {
dtemp = GET32(dev, CONC_dSRCIO_OFF);
if ((dtemp & SRC_BUSY) == 0)
break;
}
/* assert a read request */
PUT32(dev, CONC_dSRCIO_OFF, (dtemp & SRC_CTLMASK) | ((int)reg << 25));
/* now wait for the data */
for (i = 0; i < SRC_IOPOLL_COUNT; ++i) {
dtemp = GET32(dev, CONC_dSRCIO_OFF);
if ((dtemp & SRC_BUSY) == 0)
break;
}
return ((unsigned short) dtemp);
}
static void
SRCRegWrite(audioens_dev_t *dev, unsigned short reg, unsigned short val)
{
int i, dtemp;
int writeval;
dtemp = GET32(dev, CONC_dSRCIO_OFF);
/* wait for ready */
for (i = 0; i < SRC_IOPOLL_COUNT; ++i) {
dtemp = GET32(dev, CONC_dSRCIO_OFF);
if ((dtemp & SRC_BUSY) == 0)
break;
}
/* assert the write request */
writeval = (dtemp & SRC_CTLMASK) | SRC_WENABLE |
((int)reg << 25) | val;
PUT32(dev, CONC_dSRCIO_OFF, writeval);
}
static void
SRCSetRate(audioens_dev_t *dev, unsigned char base, unsigned short rate)
{
int i, freq, dtemp;
unsigned short N, truncM, truncStart;
if (base != SRC_ADC_BASE) {
/* freeze the channel */
dtemp = (base == SRC_DAC1_BASE) ?
SRC_DAC1FREEZE : SRC_DAC2FREEZE;
for (i = 0; i < SRC_IOPOLL_COUNT; ++i) {
if (!(GET32(dev, CONC_dSRCIO_OFF) & SRC_BUSY))
break;
}
PUT32(dev, CONC_dSRCIO_OFF,
(GET32(dev, CONC_dSRCIO_OFF) & SRC_CTLMASK) | dtemp);
/* calculate new frequency and write it - preserve accum */
freq = ((int)rate << 16) / 3000U;
SRCRegWrite(dev, (unsigned short) base + SRC_INT_REGS_OFF,
(SRCRegRead(dev, (unsigned short) base + SRC_INT_REGS_OFF)
& 0x00ffU) | ((unsigned short) (freq >> 6) & 0xfc00));
SRCRegWrite(dev, (unsigned short) base + SRC_VFREQ_FRAC_OFF,
(unsigned short) freq >> 1);
/* un-freeze the channel */
for (i = 0; i < SRC_IOPOLL_COUNT; ++i)
if (!(GET32(dev, CONC_dSRCIO_OFF) & SRC_BUSY))
break;
PUT32(dev, CONC_dSRCIO_OFF,
(GET32(dev, CONC_dSRCIO_OFF) & SRC_CTLMASK) & ~dtemp);
} else {
/* derive oversample ratio */
N = rate / 3000U;
if (N == 15 || N == 13 || N == 11 || N == 9)
--N;
/* truncate the filter and write n/trunc_start */
truncM = (21 * N - 1) | 1;
if (rate >= 24000U) {
if (truncM > 239)
truncM = 239;
truncStart = (239 - truncM) >> 1;
SRCRegWrite(dev, base + SRC_TRUNC_N_OFF,
(truncStart << 9) | (N << 4));
} else {
if (truncM > 119)
truncM = 119;
truncStart = (119 - truncM) >> 1;
SRCRegWrite(dev, base + SRC_TRUNC_N_OFF,
0x8000U | (truncStart << 9) | (N << 4));
}
/* calculate new frequency and write it - preserve accum */
freq = ((48000UL << 16) / rate) * N;
SRCRegWrite(dev, base + SRC_INT_REGS_OFF,
(SRCRegRead(dev, (unsigned short) base + SRC_INT_REGS_OFF)
& 0x00ff) | ((unsigned short) (freq >> 6) & 0xfc00));
SRCRegWrite(dev, base + SRC_VFREQ_FRAC_OFF,
(unsigned short) freq >> 1);
SRCRegWrite(dev, SRC_ADC_VOL_L, N << 8);
SRCRegWrite(dev, SRC_ADC_VOL_R, N << 8);
}
}
static void
SRCInit(audioens_dev_t *dev)
{
int i;
/* Clear all SRC RAM then init - keep SRC disabled until done */
for (i = 0; i < SRC_IOPOLL_COUNT; ++i) {
if (!(GET32(dev, CONC_dSRCIO_OFF) & SRC_BUSY))
break;
}
PUT32(dev, CONC_dSRCIO_OFF, SRC_DISABLE);
for (i = 0; i < 0x80; ++i)
SRCRegWrite(dev, (unsigned short) i, 0U);
SRCRegWrite(dev, SRC_DAC1_BASE + SRC_TRUNC_N_OFF, 16 << 4);
SRCRegWrite(dev, SRC_DAC1_BASE + SRC_INT_REGS_OFF, 16 << 10);
SRCRegWrite(dev, SRC_DAC2_BASE + SRC_TRUNC_N_OFF, 16 << 4);
SRCRegWrite(dev, SRC_DAC2_BASE + SRC_INT_REGS_OFF, 16 << 10);
SRCRegWrite(dev, SRC_DAC1_VOL_L, 1 << 12);
SRCRegWrite(dev, SRC_DAC1_VOL_R, 1 << 12);
SRCRegWrite(dev, SRC_DAC2_VOL_L, 1 << 12);
SRCRegWrite(dev, SRC_DAC2_VOL_R, 1 << 12);
SRCRegWrite(dev, SRC_ADC_VOL_L, 1 << 12);
SRCRegWrite(dev, SRC_ADC_VOL_R, 1 << 12);
/* default some rates */
SRCSetRate(dev, SRC_DAC1_BASE, 48000);
SRCSetRate(dev, SRC_DAC2_BASE, 48000);
SRCSetRate(dev, SRC_ADC_BASE, 48000);
/* now enable the whole deal */
for (i = 0; i < SRC_IOPOLL_COUNT; ++i) {
if (!(GET32(dev, CONC_dSRCIO_OFF) & SRC_BUSY))
break;
}
PUT32(dev, CONC_dSRCIO_OFF, 0);
}
static void
audioens_writemem(audioens_dev_t *dev, uint32_t page, uint32_t offs,
uint32_t data)
{
/* Select memory page */
PUT32(dev, CONC_bMEMPAGE_OFF, page);
PUT32(dev, offs, data);
}
static uint32_t
audioens_readmem(audioens_dev_t *dev, uint32_t page, uint32_t offs)
{
PUT32(dev, CONC_bMEMPAGE_OFF, page); /* Select memory page */
return (GET32(dev, offs));
}
static uint_t
audioens_intr(caddr_t arg1, caddr_t arg2)
{
audioens_dev_t *dev = (void *)arg1;
int stats;
int tmp;
unsigned char ackbits = 0;
audioens_port_t *port;
audio_engine_t *do_dac, *do_adc;
_NOTE(ARGUNUSED(arg2));
/*
* NB: The old audioens didn't report spurious interrupts. On
* a system with shared interrupts (typical!) there will
* normally be lots of these (each time the "other" device
* interrupts).
*
* Also, because of the way the interrupt chain handling
* works, reporting of spurious interrupts is probably not
* terribly useful.
*
* However, we can count interrupts where the master interrupt
* bit is set but none of the ackbits that we are prepared to
* process is set. That is probably useful.
*/
mutex_enter(&dev->mutex);
if (!dev->enabled) {
mutex_exit(&dev->mutex);
return (DDI_INTR_UNCLAIMED);
}
stats = GET32(dev, CONC_dSTATUS_OFF);
if (!(stats & CONC_STATUS_PENDING)) { /* No interrupt pending */
mutex_exit(&dev->mutex);
return (DDI_INTR_UNCLAIMED);
}
do_dac = do_adc = NULL;
/* DAC1 (synth) interrupt */
if (stats & CONC_STATUS_DAC1INT) {
ackbits |= CONC_SERCTL_DAC1IE;
port = &dev->port[PORT_DAC];
if (port->trigger) {
do_dac = port->engine;
}
}
/* DAC2 interrupt */
if (stats & CONC_STATUS_DAC2INT) {
ackbits |= CONC_SERCTL_DAC2IE;
}
/* ADC interrupt */
if (stats & CONC_STATUS_ADCINT) {
ackbits |= CONC_SERCTL_ADCIE;
port = &dev->port[PORT_ADC];
if (port->trigger) {
do_adc = port->engine;
}
}
/* UART interrupt - we shouldn't get this! */
if (stats & CONC_STATUS_UARTINT) {
uint8_t uart_stat = GET8(dev, CONC_bUARTCSTAT_OFF);
while (uart_stat & CONC_UART_RXRDY)
uart_stat = GET8(dev, CONC_bUARTCSTAT_OFF);
}
/* Ack the interrupt */
tmp = GET8(dev, CONC_bSERCTL_OFF);
PUT8(dev, CONC_bSERCTL_OFF, tmp & (~ackbits)); /* Clear bits */
PUT8(dev, CONC_bSERCTL_OFF, tmp | ackbits); /* Turn them back on */
if (dev->ksp) {
if (ackbits == 0) {
KSINTR(dev)->intrs[KSTAT_INTR_SPURIOUS]++;
} else {
KSINTR(dev)->intrs[KSTAT_INTR_HARD]++;
}
}
mutex_exit(&dev->mutex);
if (do_dac)
audio_engine_consume(do_dac);
if (do_adc)
audio_engine_produce(do_adc);
return (DDI_INTR_CLAIMED);
}
/*
* Audio routines
*/
static int
audioens_format(void *arg)
{
_NOTE(ARGUNUSED(arg));
/* hardware can also do AUDIO_FORMAT_U8, but no need for it */
return (AUDIO_FORMAT_S16_LE);
}
static int
audioens_channels(void *arg)
{
audioens_port_t *port = arg;
return (port->nchan);
}
static int
audioens_rate(void *arg)
{
audioens_port_t *port = arg;
return (port->speed);
}
static void
audioens_init_port(audioens_port_t *port)
{
audioens_dev_t *dev = port->dev;
unsigned tmp;
if (port->suspended)
return;
switch (port->num) {
case PORT_DAC:
/* Set physical address of the DMA buffer */
audioens_writemem(dev, CONC_DAC1CTL_PAGE, CONC_dDAC1PADDR_OFF,
port->paddr);
audioens_writemem(dev, CONC_DAC2CTL_PAGE, CONC_dDAC2PADDR_OFF,
port->paddr + (port->nframes * sizeof (int16_t) * 2));
/* Set DAC rate */
SRCSetRate(dev, SRC_DAC1_BASE, port->speed);
SRCSetRate(dev, SRC_DAC2_BASE, port->speed);
/* Configure the channel setup - SPDIF only uses front */
tmp = GET32(dev, CONC_dSTATUS_OFF);
tmp &= ~(CONC_STATUS_SPKR_MASK | CONC_STATUS_SPDIF_MASK);
tmp |= CONC_STATUS_SPKR_4CH | CONC_STATUS_SPDIF_P1;
PUT32(dev, CONC_dSTATUS_OFF, tmp);
/* Set format */
PUT8(dev, CONC_bSKIPC_OFF, 0x10);
SET8(dev, CONC_bSERFMT_OFF,
CONC_PCM_DAC1_16BIT | CONC_PCM_DAC2_16BIT |
CONC_PCM_DAC1_STEREO | CONC_PCM_DAC2_STEREO);
/* Set the frame count */
audioens_writemem(dev, CONC_DAC1CTL_PAGE, CONC_wDAC1FC_OFF,
port->nframes - 1);
audioens_writemem(dev, CONC_DAC2CTL_PAGE, CONC_wDAC2FC_OFF,
port->nframes - 1);
/* Set # of frames between interrupts */
PUT16(dev, CONC_wDAC1IC_OFF, port->fragfr - 1);
PUT16(dev, CONC_wDAC2IC_OFF, port->fragfr - 1);
break;
case PORT_ADC:
/* Set physical address of the DMA buffer */
audioens_writemem(dev, CONC_ADCCTL_PAGE, CONC_dADCPADDR_OFF,
port->paddr);
/* Set ADC rate */
SRCSetRate(dev, SRC_ADC_BASE, port->speed);
/* Set format - for input we only support 16 bit input */
tmp = GET8(dev, CONC_bSERFMT_OFF);
tmp |= CONC_PCM_ADC_16BIT;
tmp |= CONC_PCM_ADC_STEREO;
PUT8(dev, CONC_bSKIPC_OFF, 0x10);
PUT8(dev, CONC_bSERFMT_OFF, tmp);
/* Set the frame count */
audioens_writemem(dev, CONC_ADCCTL_PAGE, CONC_wADCFC_OFF,
port->nframes - 1);
/* Set # of frames between interrupts */
PUT16(dev, CONC_wADCIC_OFF, port->fragfr - 1);
break;
}
port->frameno = 0;
}
static int
audioens_open(void *arg, int flag, unsigned *fragfrp, unsigned *nfragsp,
caddr_t *bufp)
{
audioens_port_t *port = arg;
audioens_dev_t *dev = port->dev;
int intrs;
_NOTE(ARGUNUSED(flag));
mutex_enter(&dev->mutex);
if (port->num == PORT_ADC) {
intrs = dev->rintrs;
} else {
intrs = dev->pintrs;
}
/* interrupt at least at 25 Hz, and not more than 250 Hz */
intrs = min(250, max(25, intrs));
port->fragfr = (port->speed / intrs);
port->nfrags = AUDIOENS_BUF_LEN /
(port->fragfr * port->nchan * sizeof (int16_t));
port->nfrags = max(4, min(port->nfrags, 1024));
port->nframes = port->nfrags * port->fragfr;
port->trigger = B_FALSE;
port->count = 0;
audioens_init_port(port);
*fragfrp = port->fragfr;
*nfragsp = port->nfrags;
*bufp = port->kaddr;
mutex_exit(&dev->mutex);
return (0);
}
static void
audioens_start_port(audioens_port_t *port)
{
audioens_dev_t *dev = port->dev;
if (!port->suspended) {
switch (port->num) {
case PORT_DAC:
SET8(dev, CONC_bDEVCTL_OFF,
CONC_DEVCTL_DAC2_EN | CONC_DEVCTL_DAC1_EN);
SET8(dev, CONC_bSERCTL_OFF, CONC_SERCTL_DAC1IE);
break;
case PORT_ADC:
SET8(dev, CONC_bDEVCTL_OFF, CONC_DEVCTL_ADC_EN);
SET8(dev, CONC_bSERCTL_OFF, CONC_SERCTL_ADCIE);
break;
}
}
}
static void
audioens_stop_port(audioens_port_t *port)
{
audioens_dev_t *dev = port->dev;
if (!port->suspended) {
switch (port->num) {
case PORT_DAC:
CLR8(dev, CONC_bDEVCTL_OFF,
CONC_DEVCTL_DAC2_EN | CONC_DEVCTL_DAC1_EN);
CLR8(dev, CONC_bSERCTL_OFF, CONC_SERCTL_DAC1IE);
break;
case PORT_ADC:
CLR8(dev, CONC_bDEVCTL_OFF, CONC_DEVCTL_ADC_EN);
CLR8(dev, CONC_bSERCTL_OFF, CONC_SERCTL_ADCIE);
break;
}
}
}
static int
audioens_start(void *arg)
{
audioens_port_t *port = arg;
audioens_dev_t *dev = port->dev;
mutex_enter(&dev->mutex);
if (!port->trigger) {
port->trigger = B_TRUE;
audioens_start_port(port);
}
mutex_exit(&dev->mutex);
return (0);
}
static void
audioens_stop(void *arg)
{
audioens_port_t *port = arg;
audioens_dev_t *dev = port->dev;
mutex_enter(&dev->mutex);
if (port->trigger) {
port->trigger = B_FALSE;
audioens_stop_port(port);
}
mutex_exit(&dev->mutex);
}
static void
audioens_update_port(audioens_port_t *port)
{
uint32_t page, offs;
int frameno, n;
switch (port->num) {
case PORT_DAC:
page = CONC_DAC1CTL_PAGE;
offs = CONC_wDAC1FC_OFF;
break;
case PORT_ADC:
page = CONC_ADCCTL_PAGE;
offs = CONC_wADCFC_OFF;
break;
}
/*
* Note that the current frame counter is in the high nybble.
*/
frameno = audioens_readmem(port->dev, page, offs) >> 16;
n = frameno >= port->frameno ?
frameno - port->frameno :
frameno + port->nframes - port->frameno;
port->frameno = frameno;
port->count += n;
}
static uint64_t
audioens_count(void *arg)
{
audioens_port_t *port = arg;
audioens_dev_t *dev = port->dev;
uint64_t val;
mutex_enter(&dev->mutex);
if (!port->suspended) {
audioens_update_port(port);
}
val = port->count;
mutex_exit(&dev->mutex);
return (val);
}
static void
audioens_close(void *arg)
{
audioens_port_t *port = arg;
audioens_stop(port);
}
static void
audioens_sync(void *arg, unsigned nframes)
{
audioens_port_t *port = arg;
_NOTE(ARGUNUSED(nframes));
if (port->num == PORT_ADC) {
(void) ddi_dma_sync(port->dmah, 0, 0, DDI_DMA_SYNC_FORCPU);
} else {
(void) ddi_dma_sync(port->dmah, 0, 0, DDI_DMA_SYNC_FORDEV);
}
}
static size_t
audioens_qlen(void *arg)
{
audioens_port_t *port = arg;
return (port->fragfr);
}
static void
audioens_chinfo(void *arg, int chan, unsigned *offset, unsigned *incr)
{
audioens_port_t *port = arg;
if ((port->num == PORT_DAC) && (chan >= 2)) {
*offset = (port->nframes * 2) + (chan % 2);
*incr = 2;
} else {
*offset = chan;
*incr = 2;
}
}
audio_engine_ops_t audioens_engine_ops = {
AUDIO_ENGINE_VERSION, /* version number */
audioens_open,
audioens_close,
audioens_start,
audioens_stop,
audioens_count,
audioens_format,
audioens_channels,
audioens_rate,
audioens_sync,
audioens_qlen,
audioens_chinfo
};
void
audioens_init_hw(audioens_dev_t *dev)
{
int tmp;
if ((dev->devid == ENSONIQ_ES5880) ||
(dev->devid == ENSONIQ_ES5880A) ||
(dev->devid == ENSONIQ_ES5880B) ||
(dev->devid == 0x1371 && dev->revision == 7) ||
(dev->devid == 0x1371 && dev->revision >= 9)) {
/* Have a ES5880 so enable the codec manually */
tmp = GET8(dev, CONC_bINTSUMM_OFF) & 0xff;
tmp |= 0x20;
PUT8(dev, CONC_bINTSUMM_OFF, tmp);
for (int i = 0; i < 2000; i++)
drv_usecwait(10);
}
SRCInit(dev);
#if 0
PUT8(dev, CONC_bSERCTL_OFF, 0x00);
PUT8(dev, CONC_bNMIENA_OFF, 0x00); /* NMI off */
PUT8(dev, CONC_wNMISTAT_OFF, 0x00); /* PUT8? */
#endif
/*
* Turn on CODEC (UART and joystick left disabled)
*/
tmp = GET32(dev, CONC_bDEVCTL_OFF) & 0xff;
tmp &= ~(CONC_DEVCTL_PCICLK_DS | CONC_DEVCTL_XTALCLK_DS);
PUT8(dev, CONC_bDEVCTL_OFF, tmp);
PUT8(dev, CONC_bUARTCSTAT_OFF, 0x00);
/* Perform AC97 codec warm reset */
tmp = GET8(dev, CONC_bMISCCTL_OFF) & 0xff;
PUT8(dev, CONC_bMISCCTL_OFF, tmp | CONC_MISCCTL_SYNC_RES);
drv_usecwait(200);
PUT8(dev, CONC_bMISCCTL_OFF, tmp);
drv_usecwait(200);
if (dev->revision >= 4) {
/* XXX: enable SPDIF - PCM only for now */
if (audioens_spdif) {
/* enable SPDIF */
PUT32(dev, 0x04, GET32(dev, 0x04) | (1 << 18));
/* SPDIF out = data from DAC */
PUT32(dev, 0x00, GET32(dev, 0x00) | (1 << 26));
CLR32(dev, CONC_dSPDIF_OFF, CONC_SPDIF_AC3);
} else {
/* disable spdif out */
PUT32(dev, 0x04, GET32(dev, 0x04) & ~(1 << 18));
PUT32(dev, 0x00, GET32(dev, 0x00) & ~(1 << 26));
}
/* we want to run each channel independently */
CLR32(dev, CONC_dSTATUS_OFF, CONC_STATUS_ECHO);
}
dev->enabled = B_TRUE;
}
static int
audioens_init(audioens_dev_t *dev)
{
audioens_init_hw(dev);
/*
* On this hardware, we want to disable the internal speaker by
* default, if it exists. (We don't have a speakerphone on any
* of these cards, and no SPARC hardware uses it either!)
*/
(void) ddi_prop_update_int(DDI_DEV_T_NONE, dev->dip, AC97_PROP_SPEAKER,
0);
/*
* Init mixer
*/
dev->ac97 = ac97_alloc(dev->dip, audioens_rd97, audioens_wr97, dev);
if (dev->ac97 == NULL)
return (DDI_FAILURE);
if (ac97_init(dev->ac97, dev->osdev) != 0) {
return (DDI_FAILURE);
}
dev->pintrs = ddi_prop_get_int(DDI_DEV_T_ANY, dev->dip,
DDI_PROP_DONTPASS, "play-interrupts", DEFINTS);
dev->rintrs = ddi_prop_get_int(DDI_DEV_T_ANY, dev->dip,
DDI_PROP_DONTPASS, "record-interrupts", DEFINTS);
for (int i = 0; i <= PORT_MAX; i++) {
audioens_port_t *port;
unsigned caps;
unsigned dmaflags;
size_t rlen;
ddi_dma_cookie_t c;
unsigned ccnt;
port = &dev->port[i];
port->dev = dev;
switch (i) {
case PORT_DAC:
port->nchan = 4;
port->speed = 48000;
caps = ENGINE_OUTPUT_CAP;
dmaflags = DDI_DMA_WRITE | DDI_DMA_CONSISTENT;
break;
case PORT_ADC:
port->nchan = 2;
port->speed = 48000;
caps = ENGINE_INPUT_CAP;
dmaflags = DDI_DMA_READ | DDI_DMA_CONSISTENT;
break;
}
port->num = i;
/*
* Allocate DMA resources.
*/
if (ddi_dma_alloc_handle(dev->dip, &dma_attr, DDI_DMA_SLEEP,
NULL, &port->dmah) != DDI_SUCCESS) {
audio_dev_warn(dev->osdev,
"port %d: dma handle allocation failed", i);
return (DDI_FAILURE);
}
if (ddi_dma_mem_alloc(port->dmah, AUDIOENS_BUF_LEN, &buf_attr,
DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &port->kaddr,
&rlen, &port->acch) != DDI_SUCCESS) {
audio_dev_warn(dev->osdev,
"port %d: dma memory allocation failed", i);
return (DDI_FAILURE);
}
/* ensure that the buffer is zeroed out properly */
bzero(port->kaddr, rlen);
if (ddi_dma_addr_bind_handle(port->dmah, NULL, port->kaddr,
AUDIOENS_BUF_LEN, dmaflags, DDI_DMA_SLEEP, NULL,
&c, &ccnt) != DDI_DMA_MAPPED) {
audio_dev_warn(dev->osdev,
"port %d: dma binding failed", i);
return (DDI_FAILURE);
}
port->paddr = c.dmac_address;
/*
* Allocate and configure audio engine.
*/
port->engine = audio_engine_alloc(&audioens_engine_ops, caps);
if (port->engine == NULL) {
audio_dev_warn(dev->osdev,
"port %d: audio_engine_alloc failed", i);
return (DDI_FAILURE);
}
audio_engine_set_private(port->engine, port);
audio_dev_add_engine(dev->osdev, port->engine);
}
/*
* Set up kstats for interrupt reporting.
*/
dev->ksp = kstat_create(ddi_driver_name(dev->dip),
ddi_get_instance(dev->dip), ddi_driver_name(dev->dip),
"controller", KSTAT_TYPE_INTR, 1, KSTAT_FLAG_PERSISTENT);
if (dev->ksp != NULL) {
kstat_install(dev->ksp);
}
if (audio_dev_register(dev->osdev) != DDI_SUCCESS) {
audio_dev_warn(dev->osdev,
"unable to register with audio framework");
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
int
audioens_setup_interrupts(audioens_dev_t *dev)
{
int actual;
uint_t ipri;
if ((ddi_intr_alloc(dev->dip, dev->ihandle, DDI_INTR_TYPE_FIXED,
0, 1, &actual, DDI_INTR_ALLOC_NORMAL) != DDI_SUCCESS) ||
(actual != 1)) {
audio_dev_warn(dev->osdev, "can't alloc intr handle");
return (DDI_FAILURE);
}
if (ddi_intr_get_pri(dev->ihandle[0], &ipri) != DDI_SUCCESS) {
audio_dev_warn(dev->osdev, "can't determine intr priority");
(void) ddi_intr_free(dev->ihandle[0]);
dev->ihandle[0] = NULL;
return (DDI_FAILURE);
}
if (ddi_intr_add_handler(dev->ihandle[0], audioens_intr, dev,
NULL) != DDI_SUCCESS) {
audio_dev_warn(dev->osdev, "can't add intr handler");
(void) ddi_intr_free(dev->ihandle[0]);
dev->ihandle[0] = NULL;
return (DDI_FAILURE);
}
mutex_init(&dev->mutex, NULL, MUTEX_DRIVER, DDI_INTR_PRI(ipri));
return (DDI_SUCCESS);
}
void
audioens_destroy(audioens_dev_t *dev)
{
int i;
if (dev->ihandle[0] != NULL) {
(void) ddi_intr_disable(dev->ihandle[0]);
(void) ddi_intr_remove_handler(dev->ihandle[0]);
(void) ddi_intr_free(dev->ihandle[0]);
mutex_destroy(&dev->mutex);
}
if (dev->ksp != NULL) {
kstat_delete(dev->ksp);
}
/* free up ports, including DMA resources for ports */
for (i = 0; i <= PORT_MAX; i++) {
audioens_port_t *port = &dev->port[i];
if (port->paddr != 0)
(void) ddi_dma_unbind_handle(port->dmah);
if (port->acch != NULL)
ddi_dma_mem_free(&port->acch);
if (port->dmah != NULL)
ddi_dma_free_handle(&port->dmah);
if (port->engine != NULL) {
audio_dev_remove_engine(dev->osdev, port->engine);
audio_engine_free(port->engine);
}
}
if (dev->acch != NULL) {
ddi_regs_map_free(&dev->acch);
}
if (dev->ac97) {
ac97_free(dev->ac97);
}
if (dev->osdev != NULL) {
audio_dev_free(dev->osdev);
}
kmem_free(dev, sizeof (*dev));
}
int
audioens_attach(dev_info_t *dip)
{
uint16_t pci_command, vendor, device;
uint8_t revision;
audioens_dev_t *dev;
ddi_acc_handle_t pcih;
const char *chip_name;
const char *chip_vers;
dev = kmem_zalloc(sizeof (*dev), KM_SLEEP);
dev->dip = dip;
ddi_set_driver_private(dip, dev);
if (pci_config_setup(dip, &pcih) != DDI_SUCCESS) {
audio_dev_warn(dev->osdev, "pci_config_setup failed");
kmem_free(dev, sizeof (*dev));
return (DDI_FAILURE);
}
vendor = pci_config_get16(pcih, PCI_CONF_VENID);
device = pci_config_get16(pcih, PCI_CONF_DEVID);
revision = pci_config_get8(pcih, PCI_CONF_REVID);
if ((vendor != ENSONIQ_VENDOR_ID && vendor != CREATIVE_VENDOR_ID) ||
(device != ENSONIQ_ES1371 && device != ENSONIQ_ES5880 &&
device != ENSONIQ_ES5880A && device != ECTIVA_ES1938 &&
device != ENSONIQ_ES5880B))
goto err_exit;
chip_name = "AudioPCI97";
chip_vers = "unknown";
switch (device) {
case ENSONIQ_ES1371:
chip_name = "AudioPCI97";
switch (revision) {
case 0x02:
case 0x09:
default:
chip_vers = "ES1371";
break;
case 0x04:
case 0x06:
case 0x08:
chip_vers = "ES1373";
break;
case 0x07:
chip_vers = "ES5880";
break;
}
break;
case ENSONIQ_ES5880:
chip_name = "SB PCI128";
chip_vers = "ES5880";
break;
case ENSONIQ_ES5880A:
chip_name = "SB PCI128";
chip_vers = "ES5880A";
break;
case ENSONIQ_ES5880B:
chip_name = "SB PCI128";
chip_vers = "ES5880B";
break;
case ECTIVA_ES1938:
chip_name = "AudioPCI";
chip_vers = "ES1938";
break;
}
dev->revision = revision;
dev->devid = device;
dev->osdev = audio_dev_alloc(dip, 0);
if (dev->osdev == NULL) {
goto err_exit;
}
audio_dev_set_description(dev->osdev, chip_name);
audio_dev_set_version(dev->osdev, chip_vers);
/* set the PCI latency */
if ((audioens_latency == 32) || (audioens_latency == 64) ||
(audioens_latency == 96))
pci_config_put8(pcih, PCI_CONF_LATENCY_TIMER,
audioens_latency);
/* activate the device */
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);
/* map registers */
if (ddi_regs_map_setup(dip, 1, &dev->regs, 0, 0, &acc_attr,
&dev->acch) != DDI_SUCCESS) {
audio_dev_warn(dev->osdev, "can't map registers");
goto err_exit;
}
if (audioens_setup_interrupts(dev) != DDI_SUCCESS) {
audio_dev_warn(dev->osdev, "can't register interrupts");
goto err_exit;
}
/* This allocates and configures the engines */
if (audioens_init(dev) != DDI_SUCCESS) {
audio_dev_warn(dev->osdev, "can't init device");
goto err_exit;
}
(void) ddi_intr_enable(dev->ihandle[0]);
pci_config_teardown(&pcih);
ddi_report_dev(dip);
return (DDI_SUCCESS);
err_exit:
pci_config_teardown(&pcih);
audioens_destroy(dev);
return (DDI_FAILURE);
}
int
audioens_detach(audioens_dev_t *dev)
{
int tmp;
/* first unregister us from the DDI framework, might be busy */
if (audio_dev_unregister(dev->osdev) != DDI_SUCCESS)
return (DDI_FAILURE);
mutex_enter(&dev->mutex);
tmp = GET8(dev, CONC_bSERCTL_OFF) &
~(CONC_SERCTL_DAC2IE | CONC_SERCTL_DAC1IE | CONC_SERCTL_ADCIE);
PUT8(dev, CONC_bSERCTL_OFF, tmp);
PUT8(dev, CONC_bSERCTL_OFF, tmp);
PUT8(dev, CONC_bSERCTL_OFF, tmp);
PUT8(dev, CONC_bSERCTL_OFF, tmp);
tmp = GET8(dev, CONC_bDEVCTL_OFF) &
~(CONC_DEVCTL_DAC2_EN | CONC_DEVCTL_ADC_EN | CONC_DEVCTL_DAC1_EN);
PUT8(dev, CONC_bDEVCTL_OFF, tmp);
PUT8(dev, CONC_bDEVCTL_OFF, tmp);
PUT8(dev, CONC_bDEVCTL_OFF, tmp);
PUT8(dev, CONC_bDEVCTL_OFF, tmp);
dev->enabled = B_FALSE;
mutex_exit(&dev->mutex);
audioens_destroy(dev);
return (DDI_SUCCESS);
}
/*ARGSUSED*/
static int
audioens_resume(audioens_dev_t *dev)
{
/* ask framework to reset/relocate engine data */
for (int i = 0; i <= PORT_MAX; i++) {
audio_engine_reset(dev->port[i].engine);
}
/* reinitialize hardware */
audioens_init_hw(dev);
/* restore AC97 state */
ac97_resume(dev->ac97);
/* restart ports */
mutex_enter(&dev->mutex);
for (int i = 0; i < PORT_MAX; i++) {
audioens_port_t *port = &dev->port[i];
port->suspended = B_FALSE;
audioens_init_port(port);
/* possibly start it up if was going when we suspended */
if (port->trigger) {
audioens_start_port(port);
}
}
mutex_exit(&dev->mutex);
for (int i = 0; i < PORT_MAX; i++) {
audioens_port_t *port = &dev->port[i];
/* signal callbacks on resume */
if (!port->trigger)
continue;
if (port->num == PORT_ADC) {
audio_engine_produce(port->engine);
} else {
audio_engine_consume(port->engine);
}
}
return (DDI_SUCCESS);
}
/*ARGSUSED*/
static int
audioens_suspend(audioens_dev_t *dev)
{
/*
* Stop all engines/DMA data.
*/
mutex_enter(&dev->mutex);
for (int i = 0; i <= PORT_MAX; i++) {
audioens_stop_port(&dev->port[i]);
audioens_update_port(&dev->port[i]);
dev->port[i].suspended = B_TRUE;
}
dev->enabled = B_FALSE;
mutex_exit(&dev->mutex);
/*
* Framework needs to save off AC'97 state.
*/
ac97_suspend(dev->ac97);
return (DDI_SUCCESS);
}
static int
audioens_quiesce(dev_info_t *dip)
{
audioens_dev_t *dev;
uint8_t tmp;
if ((dev = ddi_get_driver_private(dip)) == NULL) {
return (DDI_FAILURE);
}
/* This disables all DMA engines and interrupts */
tmp = GET8(dev, CONC_bSERCTL_OFF) &
~(CONC_SERCTL_DAC2IE | CONC_SERCTL_DAC1IE | CONC_SERCTL_ADCIE);
PUT8(dev, CONC_bSERCTL_OFF, tmp);
PUT8(dev, CONC_bSERCTL_OFF, tmp);
PUT8(dev, CONC_bSERCTL_OFF, tmp);
PUT8(dev, CONC_bSERCTL_OFF, tmp);
tmp = GET8(dev, CONC_bDEVCTL_OFF) &
~(CONC_DEVCTL_DAC2_EN | CONC_DEVCTL_ADC_EN | CONC_DEVCTL_DAC1_EN);
PUT8(dev, CONC_bDEVCTL_OFF, tmp);
PUT8(dev, CONC_bDEVCTL_OFF, tmp);
PUT8(dev, CONC_bDEVCTL_OFF, tmp);
PUT8(dev, CONC_bDEVCTL_OFF, tmp);
return (DDI_SUCCESS);
}
static int
audioens_ddi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
audioens_dev_t *dev;
switch (cmd) {
case DDI_ATTACH:
return (audioens_attach(dip));
case DDI_RESUME:
if ((dev = ddi_get_driver_private(dip)) == NULL) {
return (DDI_FAILURE);
}
return (audioens_resume(dev));
default:
return (DDI_FAILURE);
}
}
static int
audioens_ddi_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
audioens_dev_t *dev;
if ((dev = ddi_get_driver_private(dip)) == NULL) {
return (DDI_FAILURE);
}
switch (cmd) {
case DDI_DETACH:
return (audioens_detach(dev));
case DDI_SUSPEND:
return (audioens_suspend(dev));
default:
return (DDI_FAILURE);
}
}
static int audioens_ddi_attach(dev_info_t *, ddi_attach_cmd_t);
static int audioens_ddi_detach(dev_info_t *, ddi_detach_cmd_t);
static struct dev_ops audioens_dev_ops = {
DEVO_REV, /* rev */
0, /* refcnt */
NULL, /* getinfo */
nulldev, /* identify */
nulldev, /* probe */
audioens_ddi_attach, /* attach */
audioens_ddi_detach, /* detach */
nodev, /* reset */
NULL, /* cb_ops */
NULL, /* bus_ops */
NULL, /* power */
audioens_quiesce, /* quiesce */
};
static struct modldrv audioens_modldrv = {
&mod_driverops, /* drv_modops */
"Ensoniq 1371/1373 Audio", /* linkinfo */
&audioens_dev_ops, /* dev_ops */
};
static struct modlinkage modlinkage = {
MODREV_1,
{ &audioens_modldrv, NULL }
};
int
_init(void)
{
int rv;
audio_init_ops(&audioens_dev_ops, DRVNAME);
if ((rv = mod_install(&modlinkage)) != 0) {
audio_fini_ops(&audioens_dev_ops);
}
return (rv);
}
int
_fini(void)
{
int rv;
if ((rv = mod_remove(&modlinkage)) == 0) {
audio_fini_ops(&audioens_dev_ops);
}
return (rv);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}