/* * 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 AudioPCI driver (ES1370) * * This driver is used with the original Ensoniq AudioPCI. */ /* * 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 #include #include #include "audiopci.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. */ #define ENSONIQ_VENDOR_ID 0x1274 #define CREATIVE_VENDOR_ID 0x1102 #define ENSONIQ_ES1370 0x5000 #define DEFINTS 75 #define DRVNAME "audiopci" #define INPUT_MIC 0 #define INPUT_LINEIN 1 #define INPUT_CD 2 #define INPUT_VIDEO 3 #define INPUT_PHONE 4 #define INSRCS 0x1f /* bits 0-4 */ static const char *audiopci_insrcs[] = { AUDIO_PORT_MIC, AUDIO_PORT_LINEIN, AUDIO_PORT_CD, AUDIO_PORT_VIDEO, AUDIO_PORT_PHONE, NULL }; typedef struct audiopci_port { /* Audio parameters */ boolean_t trigger; int speed; int fmt; int num; #define PORT_DAC 0 #define PORT_SYN 1 #define PORT_ADC 2 #define PORT_MAX PORT_ADC caddr_t kaddr; uint32_t paddr; ddi_acc_handle_t acch; ddi_dma_handle_t dmah; unsigned fragfr; unsigned nfrags; unsigned nframes; unsigned frameno; uint64_t count; struct audiopci_dev *dev; audio_engine_t *engine; } audiopci_port_t; typedef enum { CTL_VOLUME = 0, CTL_FRONT, CTL_MONO, CTL_MIC, CTL_LINE, CTL_CD, CTL_VID, CTL_PHONE, CTL_MICBOOST, CTL_RECSRC, CTL_MONSRC, CTL_NUM /* must be last */ } audiopci_ctrl_num_t; typedef struct audiopci_ctrl { struct audiopci_dev *dev; audio_ctrl_t *ctrl; audiopci_ctrl_num_t num; uint64_t val; } audiopci_ctrl_t; typedef struct audiopci_dev { audio_dev_t *adev; kmutex_t mutex; uint16_t devid; dev_info_t *dip; boolean_t enabled; boolean_t suspended; int pintrs; int rintrs; uint8_t ak_regs[0x20]; int micbias; /* * Controls */ audiopci_ctrl_t controls[CTL_NUM]; #if 0 audiopci_ctrl_t *micbias; #endif kstat_t *ksp; audiopci_port_t port[PORT_MAX + 1]; caddr_t regs; ddi_acc_handle_t acch; ddi_intr_handle_t ihandle[1]; } audiopci_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 AUDIOPCI_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 CLR16(dev, offset, v) PUT16(dev, offset, GET16(dev, offset) & ~(v)) #define SET16(dev, offset, v) PUT16(dev, offset, GET16(dev, offset) | (v)) #define KSINTR(dev) ((kstat_intr_t *)((dev)->ksp->ks_data)) static void audiopci_init_hw(audiopci_dev_t *); static void audiopci_init_port(audiopci_port_t *); static void audiopci_start_port(audiopci_port_t *); static void audiopci_stop_port(audiopci_port_t *); static void audiopci_update_port(audiopci_port_t *); static uint16_t audiopci_dac_rate(int); static int audiopci_add_controls(audiopci_dev_t *); static void audiopci_del_controls(audiopci_dev_t *); static void audiopci_ak_write(audiopci_dev_t *, uint16_t, uint8_t); static int audiopci_ak_wait(audiopci_dev_t *dev, uint8_t wstat) { for (int i = 4000; i; i--) { if (!(GET8(dev, CONC_bCODECSTAT_OFF) & wstat)) return (DDI_SUCCESS); drv_usecwait(10); } return (DDI_FAILURE); } static void audiopci_ak_idle(audiopci_dev_t *dev) { for (int i = 0; i < 5; i++) { if (audiopci_ak_wait(dev, CONC_CSTAT_CSTAT) == DDI_SUCCESS) return; } audio_dev_warn(dev->adev, "timed out waiting for codec to idle"); } static void audiopci_ak_write(audiopci_dev_t *dev, uint16_t addr, uint8_t data) { uint8_t wstat; if (dev->suspended) return; /* shadow the value */ dev->ak_regs[addr] = data; wstat = addr == CODEC_RESET_PWRD ? CONC_CSTAT_CWRIP : CONC_CSTAT_CSTAT; /* wait for codec to be available */ if (audiopci_ak_wait(dev, wstat) != DDI_SUCCESS) { audio_dev_warn(dev->adev, "timeout waiting for codec"); } PUT16(dev, CONC_wCODECCTL_OFF, (addr << 8) | data); } static void audiopci_writemem(audiopci_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 audiopci_readmem(audiopci_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 audiopci_intr(caddr_t arg1, caddr_t arg2) { audiopci_dev_t *dev = (void *)arg1; int stats; int tmp; unsigned char ackbits = 0; audiopci_port_t *port; audio_engine_t *do_syn, *do_dac, *do_adc; _NOTE(ARGUNUSED(arg2)); /* * NB: The old driver 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_INTSTAT_PENDING)) { /* No interrupt pending */ mutex_exit(&dev->mutex); return (DDI_INTR_UNCLAIMED); } do_syn = do_dac = do_adc = NULL; /* synth interrupt */ if (stats & CONC_INTSTAT_SYNINT) { ackbits |= CONC_SERCTL_SYNIE; port = &dev->port[PORT_SYN]; if (port->trigger) { do_syn = port->engine; } } /* DAC interrupt */ if (stats & CONC_INTSTAT_DACINT) { ackbits |= CONC_SERCTL_DACIE; port = &dev->port[PORT_DAC]; if (port->trigger) { do_dac = port->engine; } } /* ADC interrupt */ if (stats & CONC_INTSTAT_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_INTSTAT_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_syn) audio_engine_consume(do_syn); if (do_dac) audio_engine_consume(do_dac); if (do_adc) audio_engine_produce(do_adc); return (DDI_INTR_CLAIMED); } /* * Audio routines */ static int audiopci_format(void *arg) { _NOTE(ARGUNUSED(arg)); return (AUDIO_FORMAT_S16_LE); } static int audiopci_channels(void *arg) { _NOTE(ARGUNUSED(arg)); return (2); } static int audiopci_rate(void *arg) { audiopci_port_t *port = arg; return (port->speed); } static void audiopci_init_port(audiopci_port_t *port) { audiopci_dev_t *dev = port->dev; unsigned tmp; if (dev->suspended) return; switch (port->num) { case PORT_DAC: /* Set physical address of the DMA buffer */ audiopci_writemem(dev, CONC_DACCTL_PAGE, CONC_dDACPADDR_OFF, port->paddr); /* Set DAC rate */ PUT16(dev, CONC_wDACRATE_OFF, audiopci_dac_rate(48000)); /* Set format */ tmp = GET8(dev, CONC_bSERFMT_OFF); tmp |= CONC_PCM_DAC_16BIT; tmp |= CONC_PCM_DAC_STEREO; PUT8(dev, CONC_bSKIPC_OFF, 0x10); PUT8(dev, CONC_bSERFMT_OFF, tmp); /* Set the frame count */ audiopci_writemem(dev, CONC_DACCTL_PAGE, CONC_wDACFC_OFF, port->nframes - 1); /* Set # of frames between interrupts */ PUT16(dev, CONC_wDACIC_OFF, port->fragfr - 1); break; case PORT_SYN: /* Set physical address of the DMA buffer */ audiopci_writemem(dev, CONC_SYNCTL_PAGE, CONC_dSYNPADDR_OFF, port->paddr); /* Set rate - we force to 44.1 kHz */ SET8(dev, CONC_bMISCCTL_OFF, CONC_MISCCTL_SYN_44KHZ); /* Set format */ tmp = GET8(dev, CONC_bSERFMT_OFF); tmp |= CONC_PCM_SYN_16BIT; tmp |= CONC_PCM_SYN_STEREO; PUT8(dev, CONC_bSERFMT_OFF, tmp); /* Set the frame count */ audiopci_writemem(dev, CONC_SYNCTL_PAGE, CONC_wSYNFC_OFF, port->nframes - 1); /* Set # of frames between interrupts */ PUT16(dev, CONC_wSYNIC_OFF, port->fragfr - 1); break; case PORT_ADC: /* Set physical address of the DMA buffer */ audiopci_writemem(dev, CONC_ADCCTL_PAGE, CONC_dADCPADDR_OFF, port->paddr); /* Set ADC rate */ PUT16(dev, CONC_wDACRATE_OFF, audiopci_dac_rate(48000)); /* 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 */ audiopci_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 audiopci_open(void *arg, int flag, unsigned *fragfrp, unsigned *nfragsp, caddr_t *bufp) { audiopci_port_t *port = arg; audiopci_dev_t *dev = port->dev; int intrs; _NOTE(ARGUNUSED(flag)); mutex_enter(&dev->mutex); switch (port->num) { case PORT_ADC: intrs = dev->rintrs; break; case PORT_DAC: intrs = dev->pintrs; break; case PORT_SYN: intrs = dev->pintrs; break; } /* interrupt at least at 25 Hz, and not more than 250 Hz */ intrs = min(250, max(25, intrs)); /* NB: frame size = 4 (16-bit stereo) */ port->fragfr = (port->speed / intrs); port->nfrags = AUDIOPCI_BUF_LEN / (port->fragfr * 4); port->nfrags = max(4, min(port->nfrags, 1024)); port->nframes = port->nfrags * port->fragfr; port->trigger = B_FALSE; port->count = 0; audiopci_init_port(port); *fragfrp = port->fragfr; *nfragsp = port->nfrags; *bufp = port->kaddr; mutex_exit(&dev->mutex); return (0); } static void audiopci_start_port(audiopci_port_t *port) { audiopci_dev_t *dev = port->dev; if (!dev->suspended) { switch (port->num) { case PORT_DAC: SET8(dev, CONC_bDEVCTL_OFF, CONC_DEVCTL_DAC_EN); SET8(dev, CONC_bSERCTL_OFF, CONC_SERCTL_DACIE); break; case PORT_SYN: SET8(dev, CONC_bDEVCTL_OFF, CONC_DEVCTL_SYN_EN); SET8(dev, CONC_bSERCTL_OFF, CONC_SERCTL_SYNIE); break; case PORT_ADC: SET8(dev, CONC_bDEVCTL_OFF, CONC_DEVCTL_ADC_EN); SET8(dev, CONC_bSERCTL_OFF, CONC_SERCTL_ADCIE); break; } } } static void audiopci_stop_port(audiopci_port_t *port) { audiopci_dev_t *dev = port->dev; if (!dev->suspended) { switch (port->num) { case PORT_DAC: CLR8(dev, CONC_bDEVCTL_OFF, CONC_DEVCTL_DAC_EN); CLR8(dev, CONC_bSERCTL_OFF, CONC_SERCTL_DACIE); break; case PORT_SYN: CLR8(dev, CONC_bDEVCTL_OFF, CONC_DEVCTL_SYN_EN); CLR8(dev, CONC_bSERCTL_OFF, CONC_SERCTL_SYNIE); break; case PORT_ADC: CLR8(dev, CONC_bDEVCTL_OFF, CONC_DEVCTL_ADC_EN); CLR8(dev, CONC_bSERCTL_OFF, CONC_SERCTL_ADCIE); break; } } } static int audiopci_start(void *arg) { audiopci_port_t *port = arg; audiopci_dev_t *dev = port->dev; mutex_enter(&dev->mutex); if (!port->trigger) { port->trigger = B_TRUE; audiopci_start_port(port); } mutex_exit(&dev->mutex); return (0); } static void audiopci_stop(void *arg) { audiopci_port_t *port = arg; audiopci_dev_t *dev = port->dev; mutex_enter(&dev->mutex); if (port->trigger) { port->trigger = B_FALSE; audiopci_stop_port(port); } mutex_exit(&dev->mutex); } static void audiopci_update_port(audiopci_port_t *port) { uint32_t page, offs; int frameno, n; switch (port->num) { case PORT_DAC: page = CONC_DACCTL_PAGE; offs = CONC_wDACFC_OFF; break; case PORT_SYN: page = CONC_SYNCTL_PAGE; offs = CONC_wSYNFC_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 = audiopci_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 audiopci_count(void *arg) { audiopci_port_t *port = arg; audiopci_dev_t *dev = port->dev; uint64_t val; mutex_enter(&dev->mutex); if (!dev->suspended) { audiopci_update_port(port); } val = port->count; mutex_exit(&dev->mutex); return (val); } static void audiopci_close(void *arg) { audiopci_port_t *port = arg; audiopci_stop(port); } static void audiopci_sync(void *arg, unsigned nframes) { audiopci_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 audiopci_qlen(void *arg) { _NOTE(ARGUNUSED(arg)); /* * There is actually a small FIFO with 64 bytes -- 16 samples -- * onboard, but it is probably not worth the effort to report it, * and the FIFO will not always be full. */ return (0); } audio_engine_ops_t audiopci_engine_ops = { AUDIO_ENGINE_VERSION, /* version number */ audiopci_open, audiopci_close, audiopci_start, audiopci_stop, audiopci_count, audiopci_format, audiopci_channels, audiopci_rate, audiopci_sync, audiopci_qlen, NULL, }; static uint16_t audiopci_dac_rate(int samPerSec) { unsigned short usTemp; /* samPerSec /= 2; */ usTemp = (unsigned short) ((DAC_CLOCK_DIVIDE / 8) / samPerSec); if (usTemp & 0x00000001) { usTemp >>= 1; usTemp -= 1; } else { usTemp >>= 1; usTemp -= 2; } return (usTemp); } void audiopci_init_hw(audiopci_dev_t *dev) { int tmp; /* setup DAC frequency */ PUT16(dev, CONC_wDACRATE_OFF, audiopci_dac_rate(48000)); CLR8(dev, CONC_bMISCCTL_OFF, CONC_MISCCTL_CCB_INTRM); SET8(dev, CONC_bMISCCTL_OFF, CONC_MISCCTL_SYN_44KHZ); /* Turn on CODEC (UART and joystick left disabled) */ tmp = GET8(dev, CONC_bDEVCTL_OFF); tmp |= CONC_DEVCTL_SERR_DIS; tmp |= CONC_DEVCTL_CODEC_EN; PUT8(dev, CONC_bDEVCTL_OFF, tmp); /* Reset the UART */ PUT8(dev, CONC_bUARTCSTAT_OFF, 0x00); /* Disable NMI */ PUT8(dev, CONC_bNMIENA_OFF, 0); PUT16(dev, CONC_wNMISTAT_OFF, 0); /* Initialize serial interface */ PUT8(dev, CONC_bSERCTL_OFF, 0); PUT8(dev, CONC_bSERFMT_OFF, CONC_PCM_SYN_STEREO | CONC_PCM_SYN_16BIT); /* Unmute codec */ CLR8(dev, CONC_bMISCCTL_OFF, CONC_MISCCTL_MUTE); /* mixer initialization */ audiopci_ak_idle(dev); /* power/reset down the codec */ audiopci_ak_write(dev, CODEC_RESET_PWRD, 0); drv_usecwait(10); /* now powerup and bring out of reset */ audiopci_ak_write(dev, CODEC_RESET_PWRD, 0x3); audiopci_ak_idle(dev); /* enable PLL for DAC2 */ audiopci_ak_write(dev, CODEC_CLKSELECT, 0); /* select input mixer */ audiopci_ak_write(dev, CODEC_ADSELECT, 0); /* mark FM for output mixer */ audiopci_ak_write(dev, CODEC_OUT_SW1, CODEC_OUT_ENABLE_SYNTH); audiopci_ak_write(dev, CODEC_OUT_SW2, CODEC_OUT_ENABLE_WAVE); /* initialize some reasonable values for the WAVE and SYNTH inputs */ audiopci_ak_write(dev, CODEC_VOL_WAVE_L, 6); audiopci_ak_write(dev, CODEC_VOL_WAVE_R, 6); audiopci_ak_write(dev, CODEC_VOL_SYNTH_L, 6); audiopci_ak_write(dev, CODEC_VOL_SYNTH_R, 6); /* enable microphone phantom power */ if (dev->micbias) { SET16(dev, 2, CONC_DEVCTL_MICBIAS); } dev->enabled = B_TRUE; } static int audiopci_init(audiopci_dev_t *dev) { dev->micbias = 1; audiopci_init_hw(dev); 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++) { audiopci_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_SYN: caps = ENGINE_OUTPUT_CAP; dmaflags = DDI_DMA_WRITE | DDI_DMA_CONSISTENT; port->speed = 44100; break; case PORT_DAC: caps = ENGINE_OUTPUT_CAP; dmaflags = DDI_DMA_WRITE | DDI_DMA_CONSISTENT; port->speed = 48000; break; case PORT_ADC: caps = ENGINE_INPUT_CAP; dmaflags = DDI_DMA_READ | DDI_DMA_CONSISTENT; port->speed = 48000; 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->adev, "port %d: dma handle allocation failed", i); return (DDI_FAILURE); } if (ddi_dma_mem_alloc(port->dmah, AUDIOPCI_BUF_LEN, &buf_attr, DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &port->kaddr, &rlen, &port->acch) != DDI_SUCCESS) { audio_dev_warn(dev->adev, "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, AUDIOPCI_BUF_LEN, dmaflags, DDI_DMA_SLEEP, NULL, &c, &ccnt) != DDI_DMA_MAPPED) { audio_dev_warn(dev->adev, "port %d: dma binding failed", i); return (DDI_FAILURE); } port->paddr = c.dmac_address; /* * Allocate and configure audio engine. */ port->engine = audio_engine_alloc(&audiopci_engine_ops, caps); if (port->engine == NULL) { audio_dev_warn(dev->adev, "port %d: audio_engine_alloc failed", i); return (DDI_FAILURE); } audio_engine_set_private(port->engine, port); audio_dev_add_engine(dev->adev, port->engine); } /* * Register audio controls. */ if (audiopci_add_controls(dev) == DDI_FAILURE) { return (DDI_FAILURE); } /* * 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->adev) != DDI_SUCCESS) { audio_dev_warn(dev->adev, "unable to register with audio framework"); return (DDI_FAILURE); } return (DDI_SUCCESS); } int audiopci_setup_interrupts(audiopci_dev_t *dev) { int actual; uint_t ipri; if ((ddi_intr_alloc(dev->dip, dev->ihandle, DDI_INTR_TYPE_FIXED, 0, 1, &actual, DDI_INTR_ALLOC_NORMAL) != DDI_SUCCESS) || (actual != 1)) { audio_dev_warn(dev->adev, "can't alloc intr handle"); return (DDI_FAILURE); } if (ddi_intr_get_pri(dev->ihandle[0], &ipri) != DDI_SUCCESS) { audio_dev_warn(dev->adev, "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], audiopci_intr, dev, NULL) != DDI_SUCCESS) { audio_dev_warn(dev->adev, "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 audiopci_destroy(audiopci_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++) { audiopci_port_t *port = &dev->port[i]; if (port->paddr != 0) (void) ddi_dma_unbind_handle(port->dmah); if (port->acch != NULL) ddi_dma_mem_free(&port->acch); if (port->dmah != NULL) ddi_dma_free_handle(&port->dmah); if (port->engine != NULL) { audio_dev_remove_engine(dev->adev, port->engine); audio_engine_free(port->engine); } } if (dev->acch != NULL) { ddi_regs_map_free(&dev->acch); } audiopci_del_controls(dev); if (dev->adev != NULL) { audio_dev_free(dev->adev); } kmem_free(dev, sizeof (*dev)); } static void audiopci_stereo(audiopci_dev_t *dev, audiopci_ctrl_num_t num, uint8_t lreg) { uint8_t lval, rval; uint8_t lmute, rmute; uint64_t val; uint8_t rreg; rreg = lreg + 1; val = dev->controls[num].val; lval = (val & 0xff00) >> 8; rval = val & 0xff; lmute = lval ? 0 : CODEC_ATT_MUTE; rmute = rval ? 0 : CODEC_ATT_MUTE; /* convert to attenuation & apply mute if appropriate */ lval = ((((100U - lval) * CODEC_ATT_MAX) / 100) & 0xff) | lmute; rval = ((((100U - rval) * CODEC_ATT_MAX) / 100) & 0xff) | rmute; audiopci_ak_write(dev, lreg, lval); audiopci_ak_write(dev, rreg, rval); } static void audiopci_mono(audiopci_dev_t *dev, audiopci_ctrl_num_t num, uint8_t reg) { uint64_t val = (dev->controls[num].val & 0xff); uint8_t mute; mute = val ? 0 : CODEC_ATT_MUTE; val = ((((100U - val) * CODEC_ATT_MAX) / 100) & 0xff) | mute; audiopci_ak_write(dev, reg, val); } static void audiopci_mono8(audiopci_dev_t *dev, audiopci_ctrl_num_t num, uint8_t reg) { uint64_t val = (dev->controls[num].val & 0xff); uint8_t mute; mute = val ? 0 : CODEC_ATT_MUTE; val = ((((100U - val) * CODEC_ATT_MONO) / 100) & 0xff) | mute; audiopci_ak_write(dev, reg, val); } static int audiopci_get_value(void *arg, uint64_t *val) { audiopci_ctrl_t *pc = arg; audiopci_dev_t *dev = pc->dev; mutex_enter(&dev->mutex); *val = pc->val; mutex_exit(&dev->mutex); return (0); } static void audiopci_configure_output(audiopci_dev_t *dev) { uint64_t val; uint8_t tmp; /* PCM/Wave level */ audiopci_mono(dev, CTL_VOLUME, CODEC_VOL_WAVE_L); audiopci_mono(dev, CTL_VOLUME, CODEC_VOL_WAVE_R); audiopci_mono(dev, CTL_VOLUME, CODEC_VOL_SYNTH_L); audiopci_mono(dev, CTL_VOLUME, CODEC_VOL_SYNTH_R); /* front & mono outputs */ audiopci_stereo(dev, CTL_FRONT, CODEC_VOL_MASTER_L); audiopci_mono8(dev, CTL_MONO, CODEC_VOL_MONO); val = dev->controls[CTL_MONSRC].val; /* setup output monitoring as well */ tmp = CODEC_OUT_ENABLE_SYNTH; if (val & (1U << INPUT_MIC)) tmp |= CODEC_OUT_ENABLE_MIC; if (val & (1U << INPUT_CD)) tmp |= CODEC_OUT_ENABLE_CD; if (val & (1U << INPUT_LINEIN)) tmp |= CODEC_OUT_ENABLE_AUX; audiopci_ak_write(dev, CODEC_OUT_SW1, tmp); tmp = CODEC_OUT_ENABLE_WAVE; if (val & (1U << INPUT_VIDEO)) tmp |= CODEC_OUT_ENABLE_TV; if (val & (1U << INPUT_PHONE)) tmp |= CODEC_OUT_ENABLE_TAD; audiopci_ak_write(dev, CODEC_OUT_SW2, tmp); } static void audiopci_configure_input(audiopci_dev_t *dev) { uint64_t val = dev->controls[CTL_RECSRC].val; uint8_t tmp; tmp = 0; if (val & (1U << INPUT_LINEIN)) tmp |= CODEC_IN_ENABLE_AUX_L; if (val & (1U << INPUT_CD)) tmp |= CODEC_IN_ENABLE_CD_L; if (val & (1U << INPUT_MIC)) tmp |= CODEC_IN_ENABLE_MIC; if (val & (1U << INPUT_PHONE)) tmp |= CODEC_IN_ENABLE_TAD; audiopci_ak_write(dev, CODEC_LIN_SW1, tmp); tmp = 0; if (val & (1U << INPUT_LINEIN)) tmp |= CODEC_IN_ENABLE_AUX_R; if (val & (1U << INPUT_CD)) tmp |= CODEC_IN_ENABLE_CD_R; if (val & (1U << INPUT_PHONE)) tmp |= CODEC_IN_ENABLE_TAD; if (val & (1U << INPUT_MIC)) tmp |= CODEC_IN_ENABLE_MIC; audiopci_ak_write(dev, CODEC_RIN_SW1, tmp); tmp = 0; if (val & (1U << INPUT_VIDEO)) tmp |= CODEC_IN_ENABLE_TV_L; if (val & (1U << INPUT_MIC)) tmp |= CODEC_IN_ENABLE_TMIC; audiopci_ak_write(dev, CODEC_LIN_SW2, tmp); tmp = 0; if (val & (1U << INPUT_VIDEO)) tmp |= CODEC_IN_ENABLE_TV_R; if (val & (1U << INPUT_MIC)) tmp |= CODEC_IN_ENABLE_TMIC; audiopci_ak_write(dev, CODEC_RIN_SW2, tmp); /* configure volumes */ audiopci_mono(dev, CTL_MIC, CODEC_VOL_MIC); audiopci_mono(dev, CTL_PHONE, CODEC_VOL_TAD); audiopci_stereo(dev, CTL_LINE, CODEC_VOL_AUX_L); audiopci_stereo(dev, CTL_CD, CODEC_VOL_CD_L); audiopci_stereo(dev, CTL_VID, CODEC_VOL_TV_L); /* activate 30dB mic boost */ audiopci_ak_write(dev, CODEC_MICBOOST, dev->controls[CTL_MICBOOST].val ? 1 : 0); } static int audiopci_set_reclevel(void *arg, uint64_t val) { audiopci_ctrl_t *pc = arg; audiopci_dev_t *dev = pc->dev; uint8_t l; uint8_t r; l = (val & 0xff00) >> 8; r = val & 0xff; if ((l > 100) || (r > 100)) return (EINVAL); mutex_enter(&dev->mutex); pc->val = val; audiopci_configure_input(dev); mutex_exit(&dev->mutex); return (0); } static int audiopci_set_micboost(void *arg, uint64_t val) { audiopci_ctrl_t *pc = arg; audiopci_dev_t *dev = pc->dev; mutex_enter(&dev->mutex); pc->val = val; audiopci_configure_input(dev); mutex_exit(&dev->mutex); return (0); } static int audiopci_set_monsrc(void *arg, uint64_t val) { audiopci_ctrl_t *pc = arg; audiopci_dev_t *dev = pc->dev; if ((val & ~INSRCS) != 0) return (EINVAL); mutex_enter(&dev->mutex); pc->val = val; audiopci_configure_output(dev); mutex_exit(&dev->mutex); return (0); } static int audiopci_set_recsrc(void *arg, uint64_t val) { audiopci_ctrl_t *pc = arg; audiopci_dev_t *dev = pc->dev; if ((val & ~INSRCS) != 0) return (EINVAL); mutex_enter(&dev->mutex); pc->val = val; audiopci_configure_input(dev); mutex_exit(&dev->mutex); return (0); } static int audiopci_set_volume(void *arg, uint64_t val) { audiopci_ctrl_t *pc = arg; audiopci_dev_t *dev = pc->dev; val &= 0xff; if (val > 100) return (EINVAL); val = (val & 0xff) | ((val & 0xff) << 8); mutex_enter(&dev->mutex); pc->val = val; audiopci_configure_output(dev); mutex_exit(&dev->mutex); return (0); } static int audiopci_set_front(void *arg, uint64_t val) { audiopci_ctrl_t *pc = arg; audiopci_dev_t *dev = pc->dev; uint8_t l; uint8_t r; l = (val & 0xff00) >> 8; r = val & 0xff; if ((l > 100) || (r > 100)) return (EINVAL); mutex_enter(&dev->mutex); pc->val = val; audiopci_configure_output(dev); mutex_exit(&dev->mutex); return (0); } static int audiopci_set_speaker(void *arg, uint64_t val) { audiopci_ctrl_t *pc = arg; audiopci_dev_t *dev = pc->dev; val &= 0xff; if (val > 100) return (EINVAL); mutex_enter(&dev->mutex); pc->val = val; audiopci_configure_output(dev); mutex_exit(&dev->mutex); return (0); } #define PLAYCTL (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_PLAY) #define RECCTL (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_REC) #define MONCTL (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_MONITOR) #define PCMVOL (PLAYCTL | AUDIO_CTRL_FLAG_PCMVOL) #define MAINVOL (PLAYCTL | AUDIO_CTRL_FLAG_MAINVOL) #define RECVOL (RECCTL | AUDIO_CTRL_FLAG_RECVOL) static void audiopci_alloc_ctrl(audiopci_dev_t *dev, uint32_t num, uint64_t val) { audio_ctrl_desc_t desc; audio_ctrl_wr_t fn; audiopci_ctrl_t *pc; bzero(&desc, sizeof (desc)); pc = &dev->controls[num]; pc->num = num; pc->dev = dev; switch (num) { case CTL_VOLUME: desc.acd_name = AUDIO_CTRL_ID_VOLUME; desc.acd_type = AUDIO_CTRL_TYPE_MONO; desc.acd_minvalue = 0; desc.acd_maxvalue = 100; desc.acd_flags = PCMVOL; fn = audiopci_set_volume; break; case CTL_FRONT: desc.acd_name = AUDIO_CTRL_ID_LINEOUT; desc.acd_type = AUDIO_CTRL_TYPE_STEREO; desc.acd_minvalue = 0; desc.acd_maxvalue = 100; desc.acd_flags = MAINVOL; fn = audiopci_set_front; break; case CTL_MONO: desc.acd_name = AUDIO_CTRL_ID_SPEAKER; desc.acd_type = AUDIO_CTRL_TYPE_MONO; desc.acd_minvalue = 0; desc.acd_maxvalue = 100; desc.acd_flags = MAINVOL; fn = audiopci_set_speaker; break; case CTL_MIC: desc.acd_name = AUDIO_CTRL_ID_MIC; desc.acd_type = AUDIO_CTRL_TYPE_MONO; desc.acd_minvalue = 0; desc.acd_maxvalue = 100; desc.acd_flags = RECVOL; fn = audiopci_set_reclevel; break; case CTL_LINE: desc.acd_name = AUDIO_CTRL_ID_LINEIN; desc.acd_type = AUDIO_CTRL_TYPE_STEREO; desc.acd_minvalue = 0; desc.acd_maxvalue = 100; desc.acd_flags = RECVOL; fn = audiopci_set_reclevel; break; case CTL_CD: desc.acd_name = AUDIO_CTRL_ID_CD; desc.acd_type = AUDIO_CTRL_TYPE_STEREO; desc.acd_minvalue = 0; desc.acd_maxvalue = 100; desc.acd_flags = RECVOL; fn = audiopci_set_reclevel; break; case CTL_VID: desc.acd_name = AUDIO_CTRL_ID_VIDEO; desc.acd_type = AUDIO_CTRL_TYPE_STEREO; desc.acd_minvalue = 0; desc.acd_maxvalue = 100; desc.acd_flags = RECVOL; fn = audiopci_set_reclevel; break; case CTL_PHONE: desc.acd_name = AUDIO_CTRL_ID_PHONE; desc.acd_type = AUDIO_CTRL_TYPE_MONO; desc.acd_minvalue = 0; desc.acd_maxvalue = 100; desc.acd_flags = RECVOL; fn = audiopci_set_reclevel; break; case CTL_RECSRC: desc.acd_name = AUDIO_CTRL_ID_RECSRC; desc.acd_type = AUDIO_CTRL_TYPE_ENUM; desc.acd_minvalue = INSRCS; desc.acd_maxvalue = INSRCS; desc.acd_flags = RECCTL | AUDIO_CTRL_FLAG_MULTI; for (int i = 0; audiopci_insrcs[i]; i++) { desc.acd_enum[i] = audiopci_insrcs[i]; } fn = audiopci_set_recsrc; break; case CTL_MONSRC: desc.acd_name = AUDIO_CTRL_ID_MONSRC; desc.acd_type = AUDIO_CTRL_TYPE_ENUM; desc.acd_minvalue = INSRCS; desc.acd_maxvalue = INSRCS; desc.acd_flags = MONCTL | AUDIO_CTRL_FLAG_MULTI; for (int i = 0; audiopci_insrcs[i]; i++) { desc.acd_enum[i] = audiopci_insrcs[i]; } fn = audiopci_set_monsrc; break; case CTL_MICBOOST: desc.acd_name = AUDIO_CTRL_ID_MICBOOST; desc.acd_type = AUDIO_CTRL_TYPE_BOOLEAN; desc.acd_minvalue = 0; desc.acd_maxvalue = 100; desc.acd_flags = RECCTL; fn = audiopci_set_micboost; break; } pc->val = val; pc->ctrl = audio_dev_add_control(dev->adev, &desc, audiopci_get_value, fn, pc); } static int audiopci_add_controls(audiopci_dev_t *dev) { audiopci_alloc_ctrl(dev, CTL_VOLUME, 75); audiopci_alloc_ctrl(dev, CTL_FRONT, ((75) | (75 << 8))); audiopci_alloc_ctrl(dev, CTL_MONO, 75); audiopci_alloc_ctrl(dev, CTL_MIC, 50); audiopci_alloc_ctrl(dev, CTL_LINE, 0); audiopci_alloc_ctrl(dev, CTL_CD, 0); audiopci_alloc_ctrl(dev, CTL_VID, 0); audiopci_alloc_ctrl(dev, CTL_PHONE, 0); audiopci_alloc_ctrl(dev, CTL_RECSRC, (1U << INPUT_MIC)); audiopci_alloc_ctrl(dev, CTL_MONSRC, 0); audiopci_alloc_ctrl(dev, CTL_MICBOOST, 1); audiopci_configure_output(dev); audiopci_configure_input(dev); return (DDI_SUCCESS); } void audiopci_del_controls(audiopci_dev_t *dev) { for (int i = 0; i < CTL_NUM; i++) { if (dev->controls[i].ctrl) { audio_dev_del_control(dev->controls[i].ctrl); } } } int audiopci_attach(dev_info_t *dip) { uint16_t pci_command, vendor, device; audiopci_dev_t *dev; ddi_acc_handle_t pcih; 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->adev, "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); if ((vendor != ENSONIQ_VENDOR_ID && vendor != CREATIVE_VENDOR_ID) || (device != ENSONIQ_ES1370)) goto err_exit; dev->devid = device; dev->adev = audio_dev_alloc(dip, 0); if (dev->adev == NULL) { goto err_exit; } audio_dev_set_description(dev->adev, "AudioPCI"); audio_dev_set_version(dev->adev, "ES1370"); audio_dev_add_info(dev->adev, "Legacy codec: Asahi Kasei AK4531"); /* 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->adev, "can't map registers"); goto err_exit; } if (audiopci_setup_interrupts(dev) != DDI_SUCCESS) { audio_dev_warn(dev->adev, "can't register interrupts"); goto err_exit; } /* This allocates and configures the engines */ if (audiopci_init(dev) != DDI_SUCCESS) { audio_dev_warn(dev->adev, "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); audiopci_destroy(dev); return (DDI_FAILURE); } int audiopci_detach(audiopci_dev_t *dev) { int tmp; /* first unregister us from the DDI framework, might be busy */ if (audio_dev_unregister(dev->adev) != DDI_SUCCESS) return (DDI_FAILURE); mutex_enter(&dev->mutex); tmp = GET8(dev, CONC_bSERCTL_OFF) & ~(CONC_SERCTL_DACIE | CONC_SERCTL_SYNIE | 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_DAC_EN | CONC_DEVCTL_ADC_EN | CONC_DEVCTL_SYN_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); audiopci_destroy(dev); return (DDI_SUCCESS); } static int audiopci_resume(audiopci_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); } mutex_enter(&dev->mutex); dev->suspended = B_FALSE; /* reinitialize hardware */ audiopci_init_hw(dev); /* restore mixer settings */ audiopci_configure_output(dev); audiopci_configure_input(dev); /* restart ports */ for (int i = 0; i < PORT_MAX; i++) { audiopci_port_t *port = &dev->port[i]; audiopci_init_port(port); /* possibly start it up if was going when we suspended */ if (port->trigger) { audiopci_start_port(port); /* signal callbacks on resume */ if (port->num == PORT_ADC) { audio_engine_produce(port->engine); } else { audio_engine_consume(port->engine); } } } mutex_exit(&dev->mutex); return (DDI_SUCCESS); } static int audiopci_suspend(audiopci_dev_t *dev) { /* * Stop all engines/DMA data. */ mutex_enter(&dev->mutex); for (int i = 0; i <= PORT_MAX; i++) { audiopci_stop_port(&dev->port[i]); audiopci_update_port(&dev->port[i]); } dev->suspended = B_TRUE; dev->enabled = B_FALSE; mutex_exit(&dev->mutex); return (DDI_SUCCESS); } static int audiopci_quiesce(dev_info_t *dip) { audiopci_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_DACIE | CONC_SERCTL_SYNIE | 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_DAC_EN | CONC_DEVCTL_ADC_EN | CONC_DEVCTL_SYN_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 audiopci_ddi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { audiopci_dev_t *dev; switch (cmd) { case DDI_ATTACH: return (audiopci_attach(dip)); case DDI_RESUME: if ((dev = ddi_get_driver_private(dip)) == NULL) { return (DDI_FAILURE); } return (audiopci_resume(dev)); default: return (DDI_FAILURE); } } static int audiopci_ddi_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { audiopci_dev_t *dev; if ((dev = ddi_get_driver_private(dip)) == NULL) { return (DDI_FAILURE); } switch (cmd) { case DDI_DETACH: return (audiopci_detach(dev)); case DDI_SUSPEND: return (audiopci_suspend(dev)); default: return (DDI_FAILURE); } } static struct dev_ops audiopci_dev_ops = { DEVO_REV, /* rev */ 0, /* refcnt */ NULL, /* getinfo */ nulldev, /* identify */ nulldev, /* probe */ audiopci_ddi_attach, /* attach */ audiopci_ddi_detach, /* detach */ nodev, /* reset */ NULL, /* cb_ops */ NULL, /* bus_ops */ NULL, /* power */ audiopci_quiesce, /* quiesce */ }; static struct modldrv audiopci_modldrv = { &mod_driverops, /* drv_modops */ "Ensoniq 1370 Audio", /* linkinfo */ &audiopci_dev_ops, /* dev_ops */ }; static struct modlinkage modlinkage = { MODREV_1, { &audiopci_modldrv, NULL } }; int _init(void) { int rv; audio_init_ops(&audiopci_dev_ops, DRVNAME); if ((rv = mod_install(&modlinkage)) != 0) { audio_fini_ops(&audiopci_dev_ops); } return (rv); } int _fini(void) { int rv; if ((rv = mod_remove(&modlinkage)) == 0) { audio_fini_ops(&audiopci_dev_ops); } return (rv); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); }