148722b5fSGarrett D'Amore /*
248722b5fSGarrett D'Amore  * CDDL HEADER START
348722b5fSGarrett D'Amore  *
448722b5fSGarrett D'Amore  * The contents of this file are subject to the terms of the
548722b5fSGarrett D'Amore  * Common Development and Distribution License (the "License").
648722b5fSGarrett D'Amore  * You may not use this file except in compliance with the License.
748722b5fSGarrett D'Amore  *
848722b5fSGarrett D'Amore  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
948722b5fSGarrett D'Amore  * or http://www.opensolaris.org/os/licensing.
1048722b5fSGarrett D'Amore  * See the License for the specific language governing permissions
1148722b5fSGarrett D'Amore  * and limitations under the License.
1248722b5fSGarrett D'Amore  *
1348722b5fSGarrett D'Amore  * When distributing Covered Code, include this CDDL HEADER in each
1448722b5fSGarrett D'Amore  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
1548722b5fSGarrett D'Amore  * If applicable, add the following below this CDDL HEADER, with the
1648722b5fSGarrett D'Amore  * fields enclosed by brackets "[]" replaced with your own identifying
1748722b5fSGarrett D'Amore  * information: Portions Copyright [yyyy] [name of copyright owner]
1848722b5fSGarrett D'Amore  *
1948722b5fSGarrett D'Amore  * CDDL HEADER END
2048722b5fSGarrett D'Amore  */
2148722b5fSGarrett D'Amore 
2248722b5fSGarrett D'Amore /*
232c30fa45SGarrett D'Amore  * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
2448722b5fSGarrett D'Amore  */
2548722b5fSGarrett D'Amore 
2648722b5fSGarrett D'Amore /*
2748722b5fSGarrett D'Amore  * Purpose: Driver for the Creative Audigy LS sound card
2848722b5fSGarrett D'Amore  */
2948722b5fSGarrett D'Amore /*
3048722b5fSGarrett D'Amore  * Copyright (C) 4Front Technologies 1996-2009.
3148722b5fSGarrett D'Amore  */
3248722b5fSGarrett D'Amore 
3348722b5fSGarrett D'Amore #include <sys/types.h>
3448722b5fSGarrett D'Amore #include <sys/modctl.h>
3548722b5fSGarrett D'Amore #include <sys/kmem.h>
3648722b5fSGarrett D'Amore #include <sys/conf.h>
3748722b5fSGarrett D'Amore #include <sys/ddi.h>
3848722b5fSGarrett D'Amore #include <sys/sunddi.h>
3948722b5fSGarrett D'Amore #include <sys/pci.h>
4048722b5fSGarrett D'Amore #include <sys/note.h>
4148722b5fSGarrett D'Amore #include <sys/audio/audio_driver.h>
4248722b5fSGarrett D'Amore #include <sys/audio/ac97.h>
4348722b5fSGarrett D'Amore 
4448722b5fSGarrett D'Amore #include "audiols.h"
4548722b5fSGarrett D'Amore 
4648722b5fSGarrett D'Amore static struct ddi_device_acc_attr dev_attr = {
4748722b5fSGarrett D'Amore 	DDI_DEVICE_ATTR_V0,
4848722b5fSGarrett D'Amore 	DDI_STRUCTURE_LE_ACC,
4948722b5fSGarrett D'Amore 	DDI_STRICTORDER_ACC
5048722b5fSGarrett D'Amore };
5148722b5fSGarrett D'Amore 
5248722b5fSGarrett D'Amore static struct ddi_device_acc_attr buf_attr = {
5348722b5fSGarrett D'Amore 	DDI_DEVICE_ATTR_V0,
5448722b5fSGarrett D'Amore 	DDI_NEVERSWAP_ACC,
5548722b5fSGarrett D'Amore 	DDI_STRICTORDER_ACC
5648722b5fSGarrett D'Amore };
5748722b5fSGarrett D'Amore 
5848722b5fSGarrett D'Amore static ddi_dma_attr_t dma_attr_buf = {
5948722b5fSGarrett D'Amore 	DMA_ATTR_V0,		/* version number */
6048722b5fSGarrett D'Amore 	0x00000000,		/* low DMA address range */
6148722b5fSGarrett D'Amore 	0xffffffff,		/* high DMA address range */
6248722b5fSGarrett D'Amore 	0x000fffff,		/* DMA counter (16 bits only in Audigy LS) */
6348722b5fSGarrett D'Amore 	4,			/* DMA address alignment */
6448722b5fSGarrett D'Amore 	0x3c,			/* DMA burstsizes */
6548722b5fSGarrett D'Amore 	4,			/* min effective DMA size */
6648722b5fSGarrett D'Amore 	0xffffffff,		/* max DMA xfer size */
6748722b5fSGarrett D'Amore 	0xffffffff,		/* segment boundary */
6848722b5fSGarrett D'Amore 	1,			/* s/g length */
6948722b5fSGarrett D'Amore 	4,			/* granularity of device */
7048722b5fSGarrett D'Amore 	0			/* Bus specific DMA flags */
7148722b5fSGarrett D'Amore };
7248722b5fSGarrett D'Amore 
7348722b5fSGarrett D'Amore static int audigyls_attach(dev_info_t *);
7448722b5fSGarrett D'Amore static int audigyls_resume(dev_info_t *);
7548722b5fSGarrett D'Amore static int audigyls_detach(audigyls_dev_t *);
7648722b5fSGarrett D'Amore static int audigyls_suspend(audigyls_dev_t *);
7748722b5fSGarrett D'Amore 
7868c47f65SGarrett D'Amore static int audigyls_open(void *, int, unsigned *, caddr_t *);
7948722b5fSGarrett D'Amore static void audigyls_close(void *);
8048722b5fSGarrett D'Amore static int audigyls_start(void *);
8148722b5fSGarrett D'Amore static void audigyls_stop(void *);
8248722b5fSGarrett D'Amore static int audigyls_format(void *);
8348722b5fSGarrett D'Amore static int audigyls_channels(void *);
8448722b5fSGarrett D'Amore static int audigyls_rate(void *);
8548722b5fSGarrett D'Amore static uint64_t audigyls_count(void *);
8648722b5fSGarrett D'Amore static void audigyls_sync(void *, unsigned);
8748722b5fSGarrett D'Amore static void audigyls_chinfo(void *, int, unsigned *, unsigned *);
8848722b5fSGarrett D'Amore 
8948722b5fSGarrett D'Amore 
9048722b5fSGarrett D'Amore static uint16_t audigyls_read_ac97(void *, uint8_t);
9148722b5fSGarrett D'Amore static void audigyls_write_ac97(void *, uint8_t, uint16_t);
9248722b5fSGarrett D'Amore static int audigyls_alloc_port(audigyls_dev_t *, int);
9348722b5fSGarrett D'Amore static void audigyls_destroy(audigyls_dev_t *);
9448722b5fSGarrett D'Amore static void audigyls_hwinit(audigyls_dev_t *);
9548722b5fSGarrett D'Amore static void audigyls_configure_mixer(audigyls_dev_t *dev);
9648722b5fSGarrett D'Amore 
9748722b5fSGarrett D'Amore static audio_engine_ops_t audigyls_engine_ops = {
9848722b5fSGarrett D'Amore 	AUDIO_ENGINE_VERSION,
9948722b5fSGarrett D'Amore 	audigyls_open,
10048722b5fSGarrett D'Amore 	audigyls_close,
10148722b5fSGarrett D'Amore 	audigyls_start,
10248722b5fSGarrett D'Amore 	audigyls_stop,
10348722b5fSGarrett D'Amore 	audigyls_count,
10448722b5fSGarrett D'Amore 	audigyls_format,
10548722b5fSGarrett D'Amore 	audigyls_channels,
10648722b5fSGarrett D'Amore 	audigyls_rate,
10748722b5fSGarrett D'Amore 	audigyls_sync,
108f9ead4a5SGarrett D'Amore 	NULL,
109f9ead4a5SGarrett D'Amore 	audigyls_chinfo,
110f9ead4a5SGarrett D'Amore 	NULL
11148722b5fSGarrett D'Amore };
11248722b5fSGarrett D'Amore 
11348722b5fSGarrett D'Amore /*
11448722b5fSGarrett D'Amore  * Audigy LS uses AC'97 strictly for the recording side of things.
11548722b5fSGarrett D'Amore  * While the chip can supposedly route output to AC'97 for playback,
11648722b5fSGarrett D'Amore  * the PCI devices use a separate I2S DAC instead.  As a result we
11748722b5fSGarrett D'Amore  * need to suppress controls that the AC'97 codec registers.
11848722b5fSGarrett D'Amore  *
11948722b5fSGarrett D'Amore  * Furthermore, even then the AC'97 codec offers inputs that we just
12048722b5fSGarrett D'Amore  * aren't interested in.
12148722b5fSGarrett D'Amore  */
12248722b5fSGarrett D'Amore const char *audigyls_remove_ac97[] = {
12348722b5fSGarrett D'Amore 	AUDIO_CTRL_ID_VOLUME,
12448722b5fSGarrett D'Amore 	AUDIO_CTRL_ID_LINEOUT,
12548722b5fSGarrett D'Amore 	AUDIO_CTRL_ID_HEADPHONE,
12648722b5fSGarrett D'Amore 	AUDIO_CTRL_ID_CD,
12748722b5fSGarrett D'Amore 	AUDIO_CTRL_ID_VIDEO,
12848722b5fSGarrett D'Amore 	AUDIO_CTRL_ID_3DDEPTH,
12948722b5fSGarrett D'Amore 	AUDIO_CTRL_ID_3DENHANCE,
13048722b5fSGarrett D'Amore 	AUDIO_CTRL_ID_BEEP,
13148722b5fSGarrett D'Amore 	AUDIO_CTRL_ID_RECGAIN,
13248722b5fSGarrett D'Amore 	AUDIO_CTRL_ID_RECSRC,
13348722b5fSGarrett D'Amore 	AUDIO_CTRL_ID_LOOPBACK,
13448722b5fSGarrett D'Amore 	NULL,
13548722b5fSGarrett D'Amore };
13648722b5fSGarrett D'Amore 
13748722b5fSGarrett D'Amore /*
13848722b5fSGarrett D'Amore  * AC'97 sources we don't want to expose.
13948722b5fSGarrett D'Amore  */
14048722b5fSGarrett D'Amore const char *audigyls_badsrcs[] = {
14148722b5fSGarrett D'Amore 	AUDIO_PORT_VIDEO,
14248722b5fSGarrett D'Amore 	AUDIO_PORT_CD,
14348722b5fSGarrett D'Amore 	AUDIO_PORT_STEREOMIX,
14448722b5fSGarrett D'Amore 	AUDIO_PORT_MONOMIX,
14548722b5fSGarrett D'Amore 	NULL,
14648722b5fSGarrett D'Amore };
14748722b5fSGarrett D'Amore 
14848722b5fSGarrett D'Amore static unsigned int
read_chan(audigyls_dev_t * dev,int reg,int chn)14948722b5fSGarrett D'Amore read_chan(audigyls_dev_t *dev, int reg, int chn)
15048722b5fSGarrett D'Amore {
15148722b5fSGarrett D'Amore 	uint32_t val;
15248722b5fSGarrett D'Amore 
15348722b5fSGarrett D'Amore 	mutex_enter(&dev->low_mutex);
15448722b5fSGarrett D'Amore 	/* Pointer */
15548722b5fSGarrett D'Amore 	OUTL(dev, PR, (reg << 16) | (chn & 0xffff));
15648722b5fSGarrett D'Amore 	/* Data */
15748722b5fSGarrett D'Amore 	val = INL(dev, DR);
15848722b5fSGarrett D'Amore 	mutex_exit(&dev->low_mutex);
15948722b5fSGarrett D'Amore 
16048722b5fSGarrett D'Amore 	return (val);
16148722b5fSGarrett D'Amore }
16248722b5fSGarrett D'Amore 
16348722b5fSGarrett D'Amore static void
write_chan(audigyls_dev_t * dev,int reg,int chn,uint32_t value)16448722b5fSGarrett D'Amore write_chan(audigyls_dev_t *dev, int reg, int chn, uint32_t value)
16548722b5fSGarrett D'Amore {
16648722b5fSGarrett D'Amore 	mutex_enter(&dev->low_mutex);
16748722b5fSGarrett D'Amore 	/* Pointer */
16848722b5fSGarrett D'Amore 	OUTL(dev, PR, (reg << 16) | (chn & 0x7));
16948722b5fSGarrett D'Amore 	/* Data */
17048722b5fSGarrett D'Amore 	OUTL(dev, DR, value);
17148722b5fSGarrett D'Amore 	mutex_exit(&dev->low_mutex);
17248722b5fSGarrett D'Amore }
17348722b5fSGarrett D'Amore 
17448722b5fSGarrett D'Amore static unsigned int
read_reg(audigyls_dev_t * dev,int reg)17548722b5fSGarrett D'Amore read_reg(audigyls_dev_t *dev, int reg)
17648722b5fSGarrett D'Amore {
17748722b5fSGarrett D'Amore 	return (read_chan(dev, reg, 0));
17848722b5fSGarrett D'Amore }
17948722b5fSGarrett D'Amore 
18048722b5fSGarrett D'Amore static void
write_reg(audigyls_dev_t * dev,int reg,uint32_t value)18148722b5fSGarrett D'Amore write_reg(audigyls_dev_t *dev, int reg, uint32_t value)
18248722b5fSGarrett D'Amore {
18348722b5fSGarrett D'Amore 	write_chan(dev, reg, 0, value);
18448722b5fSGarrett D'Amore }
18548722b5fSGarrett D'Amore 
18648722b5fSGarrett D'Amore 
18748722b5fSGarrett D'Amore static uint16_t
audigyls_read_ac97(void * arg,uint8_t index)18848722b5fSGarrett D'Amore audigyls_read_ac97(void *arg, uint8_t index)
18948722b5fSGarrett D'Amore {
19048722b5fSGarrett D'Amore 	audigyls_dev_t *dev = arg;
19148722b5fSGarrett D'Amore 	uint16_t dtemp = 0;
19248722b5fSGarrett D'Amore 	int i;
19348722b5fSGarrett D'Amore 
19448722b5fSGarrett D'Amore 	mutex_enter(&dev->low_mutex);
19548722b5fSGarrett D'Amore 	OUTB(dev, AC97A, index);
19648722b5fSGarrett D'Amore 	for (i = 0; i < 10000; i++) {
19748722b5fSGarrett D'Amore 		if (INB(dev, AC97A) & 0x80)
19848722b5fSGarrett D'Amore 			break;
19948722b5fSGarrett D'Amore 	}
20048722b5fSGarrett D'Amore 	if (i == 10000) {	/* Timeout */
20148722b5fSGarrett D'Amore 		mutex_exit(&dev->low_mutex);
20248722b5fSGarrett D'Amore 		return (0xffff);
20348722b5fSGarrett D'Amore 	}
20448722b5fSGarrett D'Amore 	dtemp = INW(dev, AC97D);
20548722b5fSGarrett D'Amore 	mutex_exit(&dev->low_mutex);
20648722b5fSGarrett D'Amore 
20748722b5fSGarrett D'Amore 	return (dtemp);
20848722b5fSGarrett D'Amore }
20948722b5fSGarrett D'Amore 
21048722b5fSGarrett D'Amore static void
audigyls_write_ac97(void * arg,uint8_t index,uint16_t data)21148722b5fSGarrett D'Amore audigyls_write_ac97(void *arg, uint8_t index, uint16_t data)
21248722b5fSGarrett D'Amore {
21348722b5fSGarrett D'Amore 	audigyls_dev_t *dev = arg;
21448722b5fSGarrett D'Amore 	int i;
21548722b5fSGarrett D'Amore 
21648722b5fSGarrett D'Amore 	mutex_enter(&dev->low_mutex);
21748722b5fSGarrett D'Amore 	OUTB(dev, AC97A, index);
21848722b5fSGarrett D'Amore 	for (i = 0; i < 50000; i++) {
21948722b5fSGarrett D'Amore 		if (INB(dev, AC97A) & 0x80)
22048722b5fSGarrett D'Amore 			break;
22148722b5fSGarrett D'Amore 	}
22248722b5fSGarrett D'Amore 	if (i == 50000) {
22348722b5fSGarrett D'Amore 		mutex_exit(&dev->low_mutex);
22448722b5fSGarrett D'Amore 		return;
22548722b5fSGarrett D'Amore 	}
22648722b5fSGarrett D'Amore 	OUTW(dev, AC97D, data);
22748722b5fSGarrett D'Amore 	mutex_exit(&dev->low_mutex);
22848722b5fSGarrett D'Amore }
22948722b5fSGarrett D'Amore 
23048722b5fSGarrett D'Amore static void
select_digital_enable(audigyls_dev_t * dev,int mode)23148722b5fSGarrett D'Amore select_digital_enable(audigyls_dev_t *dev, int mode)
23248722b5fSGarrett D'Amore {
23348722b5fSGarrett D'Amore 	/*
23448722b5fSGarrett D'Amore 	 * Set the out3/spdif combo jack format.
23548722b5fSGarrett D'Amore 	 * mode0=analog rear/center, 1=spdif
23648722b5fSGarrett D'Amore 	 */
23748722b5fSGarrett D'Amore 
23848722b5fSGarrett D'Amore 	if (mode == 0) {
23948722b5fSGarrett D'Amore 		write_reg(dev, SPC, 0x00000f00);
24048722b5fSGarrett D'Amore 	} else {
24148722b5fSGarrett D'Amore 		write_reg(dev, SPC, 0x0000000f);
24248722b5fSGarrett D'Amore 	}
24348722b5fSGarrett D'Amore }
24448722b5fSGarrett D'Amore 
24548722b5fSGarrett D'Amore /* only for SBLive 7.1 */
246a60cc674SGarrett D'Amore void
audigyls_i2c_write(audigyls_dev_t * dev,int reg,int data)24748722b5fSGarrett D'Amore audigyls_i2c_write(audigyls_dev_t *dev, int reg, int data)
24848722b5fSGarrett D'Amore {
24948722b5fSGarrett D'Amore 	int i, timeout, tmp;
25048722b5fSGarrett D'Amore 
25148722b5fSGarrett D'Amore 	tmp = (reg << 9 | data) << 16;	/* set the upper 16 bits */
25248722b5fSGarrett D'Amore 	/* first write the command to the data reg */
25348722b5fSGarrett D'Amore 	write_reg(dev, I2C_1, tmp);
25448722b5fSGarrett D'Amore 	for (i = 0; i < 20; i++) {
25548722b5fSGarrett D'Amore 		tmp = read_reg(dev, I2C_A) & ~0x6fe;
25648722b5fSGarrett D'Amore 		/* see audigyls.pdf for bits */
25748722b5fSGarrett D'Amore 		tmp |= 0x400 | 0x100 | 0x34;
25848722b5fSGarrett D'Amore 		write_reg(dev, I2C_A, tmp);
25948722b5fSGarrett D'Amore 		/* now wait till controller sets valid bit (0x100) to 0 */
26048722b5fSGarrett D'Amore 		timeout = 0;
26148722b5fSGarrett D'Amore 		for (;;) {
26248722b5fSGarrett D'Amore 			tmp = read_reg(dev, I2C_A);
26348722b5fSGarrett D'Amore 			if ((tmp & 0x100) == 0)
26448722b5fSGarrett D'Amore 				break;
26548722b5fSGarrett D'Amore 
26648722b5fSGarrett D'Amore 			if (timeout > 100)
26748722b5fSGarrett D'Amore 				break;
26848722b5fSGarrett D'Amore 
26948722b5fSGarrett D'Amore 			timeout++;
27048722b5fSGarrett D'Amore 		}
27148722b5fSGarrett D'Amore 
27248722b5fSGarrett D'Amore 		/* transaction aborted */
27348722b5fSGarrett D'Amore 		if (tmp & 0x200)
274a60cc674SGarrett D'Amore 			break;
27548722b5fSGarrett D'Amore 	}
27648722b5fSGarrett D'Amore }
27748722b5fSGarrett D'Amore 
27848722b5fSGarrett D'Amore int
audigyls_spi_write(audigyls_dev_t * dev,int data)27948722b5fSGarrett D'Amore audigyls_spi_write(audigyls_dev_t *dev, int data)
28048722b5fSGarrett D'Amore {
28148722b5fSGarrett D'Amore 	unsigned int orig;
28248722b5fSGarrett D'Amore 	unsigned int tmp;
28348722b5fSGarrett D'Amore 	int i, valid;
28448722b5fSGarrett D'Amore 
28548722b5fSGarrett D'Amore 	tmp = read_reg(dev, SPI);
28648722b5fSGarrett D'Amore 	orig = (tmp & ~0x3ffff) | 0x30000;
28748722b5fSGarrett D'Amore 	write_reg(dev, SPI, orig | data);
28848722b5fSGarrett D'Amore 	valid = 0;
28948722b5fSGarrett D'Amore 	/* Wait for status bit to return to 0 */
29048722b5fSGarrett D'Amore 	for (i = 0; i < 1000; i++) {
29148722b5fSGarrett D'Amore 		drv_usecwait(100);
29248722b5fSGarrett D'Amore 		tmp = read_reg(dev, SPI);
29348722b5fSGarrett D'Amore 		if (!(tmp & 0x10000)) {
29448722b5fSGarrett D'Amore 			valid = 1;
29548722b5fSGarrett D'Amore 			break;
29648722b5fSGarrett D'Amore 		}
29748722b5fSGarrett D'Amore 	}
29848722b5fSGarrett D'Amore 	if (!valid)			/* Timed out */
29948722b5fSGarrett D'Amore 		return (0);
30048722b5fSGarrett D'Amore 
30148722b5fSGarrett D'Amore 	return (1);
30248722b5fSGarrett D'Amore }
30348722b5fSGarrett D'Amore 
30448722b5fSGarrett D'Amore /*
30548722b5fSGarrett D'Amore  * Audio routines
30648722b5fSGarrett D'Amore  */
30748722b5fSGarrett D'Amore 
30848722b5fSGarrett D'Amore int
audigyls_open(void * arg,int flag,unsigned * nframesp,caddr_t * bufp)30968c47f65SGarrett D'Amore audigyls_open(void *arg, int flag, unsigned *nframesp, caddr_t *bufp)
31048722b5fSGarrett D'Amore {
31148722b5fSGarrett D'Amore 	audigyls_port_t	 *port = arg;
31248722b5fSGarrett D'Amore 	audigyls_dev_t	 *dev = port->dev;
31348722b5fSGarrett D'Amore 
31448722b5fSGarrett D'Amore 	_NOTE(ARGUNUSED(flag));
31548722b5fSGarrett D'Amore 
31648722b5fSGarrett D'Amore 	mutex_enter(&dev->mutex);
31748722b5fSGarrett D'Amore 
31848722b5fSGarrett D'Amore 	port->count = 0;
31968c47f65SGarrett D'Amore 	*nframesp = port->buf_frames;
32048722b5fSGarrett D'Amore 	*bufp = port->buf_kaddr;
32148722b5fSGarrett D'Amore 	mutex_exit(&dev->mutex);
32248722b5fSGarrett D'Amore 
32348722b5fSGarrett D'Amore 	return (0);
32448722b5fSGarrett D'Amore }
32548722b5fSGarrett D'Amore 
32648722b5fSGarrett D'Amore void
audigyls_close(void * arg)32748722b5fSGarrett D'Amore audigyls_close(void *arg)
32848722b5fSGarrett D'Amore {
32968c47f65SGarrett D'Amore 	_NOTE(ARGUNUSED(arg));
33048722b5fSGarrett D'Amore }
33148722b5fSGarrett D'Amore 
33248722b5fSGarrett D'Amore int
audigyls_start(void * arg)33348722b5fSGarrett D'Amore audigyls_start(void *arg)
33448722b5fSGarrett D'Amore {
33548722b5fSGarrett D'Amore 	audigyls_port_t *port = arg;
33648722b5fSGarrett D'Amore 	audigyls_dev_t	*dev = port->dev;
33768c47f65SGarrett D'Amore 	uint32_t	tmp;
33848722b5fSGarrett D'Amore 
33948722b5fSGarrett D'Amore 	mutex_enter(&dev->mutex);
34068c47f65SGarrett D'Amore 
34168c47f65SGarrett D'Amore 	port->offset = 0;
34268c47f65SGarrett D'Amore 
34368c47f65SGarrett D'Amore 	switch (port->direction) {
34468c47f65SGarrett D'Amore 	case AUDIGYLS_PLAY_PORT:
34568c47f65SGarrett D'Amore 		write_chan(dev, PTCA, 0, 0);
34668c47f65SGarrett D'Amore 		write_chan(dev, CPFA, 0, 0);
34768c47f65SGarrett D'Amore 		write_chan(dev, CPCAV, 0, 0);
34868c47f65SGarrett D'Amore 		write_chan(dev, PTCA, 1, 0);
34968c47f65SGarrett D'Amore 		write_chan(dev, CPFA, 1, 0);
35068c47f65SGarrett D'Amore 		write_chan(dev, CPCAV, 1, 0);
35168c47f65SGarrett D'Amore 		write_chan(dev, PTCA, 3, 0);
35268c47f65SGarrett D'Amore 		write_chan(dev, CPFA, 3, 0);
35368c47f65SGarrett D'Amore 		write_chan(dev, CPCAV, 3, 0);
35468c47f65SGarrett D'Amore 
35568c47f65SGarrett D'Amore 		tmp = read_reg(dev, SA);
35668c47f65SGarrett D'Amore 		tmp |= SA_SPA(0);
35768c47f65SGarrett D'Amore 		tmp |= SA_SPA(1);
35868c47f65SGarrett D'Amore 		tmp |= SA_SPA(3);
35968c47f65SGarrett D'Amore 		write_reg(dev, SA, tmp);
36068c47f65SGarrett D'Amore 		break;
36168c47f65SGarrett D'Amore 
36268c47f65SGarrett D'Amore 	case AUDIGYLS_REC_PORT:
36368c47f65SGarrett D'Amore 		write_chan(dev, CRFA, 2, 0);
36468c47f65SGarrett D'Amore 		write_chan(dev, CRCAV, 2, 0);
36568c47f65SGarrett D'Amore 
36668c47f65SGarrett D'Amore 		tmp = read_reg(dev, SA);
36768c47f65SGarrett D'Amore 		tmp |= SA_SRA(2);
36868c47f65SGarrett D'Amore 		write_reg(dev, SA, tmp);
36968c47f65SGarrett D'Amore 		break;
37048722b5fSGarrett D'Amore 	}
37168c47f65SGarrett D'Amore 
37248722b5fSGarrett D'Amore 	mutex_exit(&dev->mutex);
37348722b5fSGarrett D'Amore 	return (0);
37448722b5fSGarrett D'Amore }
37548722b5fSGarrett D'Amore 
37648722b5fSGarrett D'Amore void
audigyls_stop(void * arg)37748722b5fSGarrett D'Amore audigyls_stop(void *arg)
37848722b5fSGarrett D'Amore {
37948722b5fSGarrett D'Amore 	audigyls_port_t	*port = arg;
38048722b5fSGarrett D'Amore 	audigyls_dev_t	*dev = port->dev;
38168c47f65SGarrett D'Amore 	uint32_t	tmp;
38248722b5fSGarrett D'Amore 
38348722b5fSGarrett D'Amore 	mutex_enter(&dev->mutex);
38468c47f65SGarrett D'Amore 
38568c47f65SGarrett D'Amore 	switch (port->direction) {
38668c47f65SGarrett D'Amore 	case AUDIGYLS_PLAY_PORT:
38768c47f65SGarrett D'Amore 		tmp = read_reg(dev, SA);
38868c47f65SGarrett D'Amore 		tmp &= ~SA_SPA(0);
38968c47f65SGarrett D'Amore 		tmp &= ~SA_SPA(1);
39068c47f65SGarrett D'Amore 		tmp &= ~SA_SPA(3);
39168c47f65SGarrett D'Amore 		write_reg(dev, SA, tmp);
39268c47f65SGarrett D'Amore 		break;
39368c47f65SGarrett D'Amore 
39468c47f65SGarrett D'Amore 	case AUDIGYLS_REC_PORT:
39568c47f65SGarrett D'Amore 		tmp = read_reg(dev, SA);
39668c47f65SGarrett D'Amore 		tmp &= ~SA_SRA(2);
39768c47f65SGarrett D'Amore 		write_reg(dev, SA, tmp);
39868c47f65SGarrett D'Amore 		break;
39948722b5fSGarrett D'Amore 	}
40068c47f65SGarrett D'Amore 
40148722b5fSGarrett D'Amore 	mutex_exit(&dev->mutex);
40248722b5fSGarrett D'Amore }
40348722b5fSGarrett D'Amore 
40448722b5fSGarrett D'Amore int
audigyls_format(void * arg)40548722b5fSGarrett D'Amore audigyls_format(void *arg)
40648722b5fSGarrett D'Amore {
40748722b5fSGarrett D'Amore 	_NOTE(ARGUNUSED(arg));
40848722b5fSGarrett D'Amore 
40948722b5fSGarrett D'Amore 	return (AUDIO_FORMAT_S16_LE);
41048722b5fSGarrett D'Amore }
41148722b5fSGarrett D'Amore 
41248722b5fSGarrett D'Amore int
audigyls_channels(void * arg)41348722b5fSGarrett D'Amore audigyls_channels(void *arg)
41448722b5fSGarrett D'Amore {
41548722b5fSGarrett D'Amore 	audigyls_port_t	*port = arg;
41648722b5fSGarrett D'Amore 
41748722b5fSGarrett D'Amore 	return (port->nchan);
41848722b5fSGarrett D'Amore }
41948722b5fSGarrett D'Amore 
42048722b5fSGarrett D'Amore int
audigyls_rate(void * arg)42148722b5fSGarrett D'Amore audigyls_rate(void *arg)
42248722b5fSGarrett D'Amore {
42348722b5fSGarrett D'Amore 	_NOTE(ARGUNUSED(arg));
42448722b5fSGarrett D'Amore 
42548722b5fSGarrett D'Amore 	return (48000);
42648722b5fSGarrett D'Amore }
42748722b5fSGarrett D'Amore 
42848722b5fSGarrett D'Amore void
audigyls_sync(void * arg,unsigned nframes)42948722b5fSGarrett D'Amore audigyls_sync(void *arg, unsigned nframes)
43048722b5fSGarrett D'Amore {
43148722b5fSGarrett D'Amore 	audigyls_port_t *port = arg;
43248722b5fSGarrett D'Amore 	_NOTE(ARGUNUSED(nframes));
43348722b5fSGarrett D'Amore 
43448722b5fSGarrett D'Amore 	(void) ddi_dma_sync(port->buf_dmah, 0, 0, port->syncdir);
43548722b5fSGarrett D'Amore }
43648722b5fSGarrett D'Amore 
43748722b5fSGarrett D'Amore uint64_t
audigyls_count(void * arg)43848722b5fSGarrett D'Amore audigyls_count(void *arg)
43948722b5fSGarrett D'Amore {
44048722b5fSGarrett D'Amore 	audigyls_port_t	*port = arg;
44148722b5fSGarrett D'Amore 	audigyls_dev_t	*dev = port->dev;
44248722b5fSGarrett D'Amore 	uint64_t	count;
44368c47f65SGarrett D'Amore 	uint32_t	offset, n;
44448722b5fSGarrett D'Amore 
44548722b5fSGarrett D'Amore 	mutex_enter(&dev->mutex);
44668c47f65SGarrett D'Amore 
44768c47f65SGarrett D'Amore 	if (port->direction == AUDIGYLS_PLAY_PORT) {
44868c47f65SGarrett D'Amore 		offset = read_chan(dev, CPFA, 0);
44968c47f65SGarrett D'Amore 	} else {
45068c47f65SGarrett D'Amore 		offset = read_chan(dev, CRFA, 2);
45168c47f65SGarrett D'Amore 	}
45268c47f65SGarrett D'Amore 
45368c47f65SGarrett D'Amore 	/* get the offset, and switch to frames */
45468c47f65SGarrett D'Amore 	offset /= (2 * sizeof (uint16_t));
45568c47f65SGarrett D'Amore 
45668c47f65SGarrett D'Amore 	if (offset >= port->offset) {
45768c47f65SGarrett D'Amore 		n = offset - port->offset;
45868c47f65SGarrett D'Amore 	} else {
45968c47f65SGarrett D'Amore 		n = offset + (port->buf_frames - port->offset);
46068c47f65SGarrett D'Amore 	}
46168c47f65SGarrett D'Amore 	port->offset = offset;
46268c47f65SGarrett D'Amore 	port->count += n;
46368c47f65SGarrett D'Amore 
46448722b5fSGarrett D'Amore 	count = port->count;
46548722b5fSGarrett D'Amore 	mutex_exit(&dev->mutex);
46648722b5fSGarrett D'Amore 	return (count);
46748722b5fSGarrett D'Amore }
46848722b5fSGarrett D'Amore 
46948722b5fSGarrett D'Amore static void
audigyls_chinfo(void * arg,int chan,unsigned * offset,unsigned * incr)47048722b5fSGarrett D'Amore audigyls_chinfo(void *arg, int chan, unsigned *offset, unsigned *incr)
47148722b5fSGarrett D'Amore {
47248722b5fSGarrett D'Amore 	audigyls_port_t *port = arg;
47348722b5fSGarrett D'Amore 
47448722b5fSGarrett D'Amore 	if (port->direction == AUDIGYLS_PLAY_PORT) {
47548722b5fSGarrett D'Amore 		*offset = (port->buf_frames * 2 * (chan / 2)) + (chan % 2);
47648722b5fSGarrett D'Amore 		*incr = 2;
47748722b5fSGarrett D'Amore 	} else {
47848722b5fSGarrett D'Amore 		*offset = chan;
47948722b5fSGarrett D'Amore 		*incr = 2;
48048722b5fSGarrett D'Amore 	}
48148722b5fSGarrett D'Amore }
48248722b5fSGarrett D'Amore 
48348722b5fSGarrett D'Amore /* private implementation bits */
48448722b5fSGarrett D'Amore 
48548722b5fSGarrett D'Amore int
audigyls_alloc_port(audigyls_dev_t * dev,int num)48648722b5fSGarrett D'Amore audigyls_alloc_port(audigyls_dev_t *dev, int num)
48748722b5fSGarrett D'Amore {
48848722b5fSGarrett D'Amore 	audigyls_port_t		*port;
48948722b5fSGarrett D'Amore 	size_t			len;
49048722b5fSGarrett D'Amore 	ddi_dma_cookie_t	cookie;
49148722b5fSGarrett D'Amore 	uint_t			count;
49248722b5fSGarrett D'Amore 	int			dir;
49348722b5fSGarrett D'Amore 	unsigned		caps;
49448722b5fSGarrett D'Amore 	audio_dev_t		*adev;
49548722b5fSGarrett D'Amore 
49648722b5fSGarrett D'Amore 	adev = dev->adev;
49748722b5fSGarrett D'Amore 	port = kmem_zalloc(sizeof (*port), KM_SLEEP);
49848722b5fSGarrett D'Amore 	dev->port[num] = port;
49948722b5fSGarrett D'Amore 	port->dev = dev;
50048722b5fSGarrett D'Amore 	port->direction = num;
50148722b5fSGarrett D'Amore 
50248722b5fSGarrett D'Amore 	switch (num) {
50348722b5fSGarrett D'Amore 	case AUDIGYLS_REC_PORT:
50448722b5fSGarrett D'Amore 		port->syncdir = DDI_DMA_SYNC_FORKERNEL;
50548722b5fSGarrett D'Amore 		caps = ENGINE_INPUT_CAP;
50648722b5fSGarrett D'Amore 		dir = DDI_DMA_READ;
50748722b5fSGarrett D'Amore 		port->nchan = 2;
50848722b5fSGarrett D'Amore 		break;
50948722b5fSGarrett D'Amore 	case AUDIGYLS_PLAY_PORT:
51048722b5fSGarrett D'Amore 		port->syncdir = DDI_DMA_SYNC_FORDEV;
51148722b5fSGarrett D'Amore 		caps = ENGINE_OUTPUT_CAP;
51248722b5fSGarrett D'Amore 		dir = DDI_DMA_WRITE;
51348722b5fSGarrett D'Amore 		port->nchan = 6;
51448722b5fSGarrett D'Amore 		break;
51548722b5fSGarrett D'Amore 	default:
51648722b5fSGarrett D'Amore 		return (DDI_FAILURE);
51748722b5fSGarrett D'Amore 	}
51848722b5fSGarrett D'Amore 
51968c47f65SGarrett D'Amore 	port->buf_frames = 2048;
52068c47f65SGarrett D'Amore 	port->buf_size = port->buf_frames * port->nchan * sizeof (int16_t);
52148722b5fSGarrett D'Amore 
52248722b5fSGarrett D'Amore 	/* Alloc buffers */
52348722b5fSGarrett D'Amore 	if (ddi_dma_alloc_handle(dev->dip, &dma_attr_buf, DDI_DMA_SLEEP, NULL,
52448722b5fSGarrett D'Amore 	    &port->buf_dmah) != DDI_SUCCESS) {
52548722b5fSGarrett D'Amore 		audio_dev_warn(adev, "failed to allocate BUF handle");
52648722b5fSGarrett D'Amore 		return (DDI_FAILURE);
52748722b5fSGarrett D'Amore 	}
52848722b5fSGarrett D'Amore 
52948722b5fSGarrett D'Amore 	if (ddi_dma_mem_alloc(port->buf_dmah, port->buf_size,
53048722b5fSGarrett D'Amore 	    &buf_attr, DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL,
53148722b5fSGarrett D'Amore 	    &port->buf_kaddr, &len, &port->buf_acch) != DDI_SUCCESS) {
53248722b5fSGarrett D'Amore 		audio_dev_warn(adev, "failed to allocate BUF memory");
53348722b5fSGarrett D'Amore 		return (DDI_FAILURE);
53448722b5fSGarrett D'Amore 	}
53548722b5fSGarrett D'Amore 
53648722b5fSGarrett D'Amore 	if (ddi_dma_addr_bind_handle(port->buf_dmah, NULL, port->buf_kaddr,
53748722b5fSGarrett D'Amore 	    len, DDI_DMA_CONSISTENT | dir, DDI_DMA_SLEEP, NULL, &cookie,
53848722b5fSGarrett D'Amore 	    &count) != DDI_SUCCESS) {
53948722b5fSGarrett D'Amore 		audio_dev_warn(adev, "failed binding BUF DMA handle");
54048722b5fSGarrett D'Amore 		return (DDI_FAILURE);
54148722b5fSGarrett D'Amore 	}
54248722b5fSGarrett D'Amore 	port->buf_paddr = cookie.dmac_address;
54348722b5fSGarrett D'Amore 
54448722b5fSGarrett D'Amore 	port->engine = audio_engine_alloc(&audigyls_engine_ops, caps);
54548722b5fSGarrett D'Amore 	if (port->engine == NULL) {
54648722b5fSGarrett D'Amore 		audio_dev_warn(adev, "audio_engine_alloc failed");
54748722b5fSGarrett D'Amore 		return (DDI_FAILURE);
54848722b5fSGarrett D'Amore 	}
54948722b5fSGarrett D'Amore 
55048722b5fSGarrett D'Amore 	audio_engine_set_private(port->engine, port);
55148722b5fSGarrett D'Amore 	audio_dev_add_engine(adev, port->engine);
55248722b5fSGarrett D'Amore 
55348722b5fSGarrett D'Amore 	return (DDI_SUCCESS);
55448722b5fSGarrett D'Amore }
55548722b5fSGarrett D'Amore 
5564a20ca87SGarrett D'Amore void
audigyls_del_controls(audigyls_dev_t * dev)5574a20ca87SGarrett D'Amore audigyls_del_controls(audigyls_dev_t *dev)
5584a20ca87SGarrett D'Amore {
5594a20ca87SGarrett D'Amore 	for (int i = 0; i < CTL_NUM; i++) {
5604a20ca87SGarrett D'Amore 		if (dev->controls[i].ctrl) {
5614a20ca87SGarrett D'Amore 			audio_dev_del_control(dev->controls[i].ctrl);
5624a20ca87SGarrett D'Amore 			dev->controls[i].ctrl = NULL;
5634a20ca87SGarrett D'Amore 		}
5644a20ca87SGarrett D'Amore 	}
5654a20ca87SGarrett D'Amore }
5664a20ca87SGarrett D'Amore 
56748722b5fSGarrett D'Amore void
audigyls_destroy(audigyls_dev_t * dev)56848722b5fSGarrett D'Amore audigyls_destroy(audigyls_dev_t *dev)
56948722b5fSGarrett D'Amore {
57068c47f65SGarrett D'Amore 	mutex_destroy(&dev->mutex);
57168c47f65SGarrett D'Amore 	mutex_destroy(&dev->low_mutex);
57248722b5fSGarrett D'Amore 
57348722b5fSGarrett D'Amore 	for (int i = 0; i < AUDIGYLS_NUM_PORT; i++) {
57448722b5fSGarrett D'Amore 		audigyls_port_t *port = dev->port[i];
57548722b5fSGarrett D'Amore 		if (!port)
57648722b5fSGarrett D'Amore 			continue;
57748722b5fSGarrett D'Amore 		if (port->engine) {
57848722b5fSGarrett D'Amore 			audio_dev_remove_engine(dev->adev, port->engine);
57948722b5fSGarrett D'Amore 			audio_engine_free(port->engine);
58048722b5fSGarrett D'Amore 		}
58148722b5fSGarrett D'Amore 		if (port->buf_paddr) {
58248722b5fSGarrett D'Amore 			(void) ddi_dma_unbind_handle(port->buf_dmah);
58348722b5fSGarrett D'Amore 		}
58448722b5fSGarrett D'Amore 		if (port->buf_acch) {
58548722b5fSGarrett D'Amore 			ddi_dma_mem_free(&port->buf_acch);
58648722b5fSGarrett D'Amore 		}
58748722b5fSGarrett D'Amore 		if (port->buf_dmah) {
58848722b5fSGarrett D'Amore 			ddi_dma_free_handle(&port->buf_dmah);
58948722b5fSGarrett D'Amore 		}
59048722b5fSGarrett D'Amore 		kmem_free(port, sizeof (*port));
59148722b5fSGarrett D'Amore 	}
59248722b5fSGarrett D'Amore 
59348722b5fSGarrett D'Amore 	if (dev->ac97 != NULL) {
59448722b5fSGarrett D'Amore 		ac97_free(dev->ac97);
59548722b5fSGarrett D'Amore 	}
5964a20ca87SGarrett D'Amore 
5974a20ca87SGarrett D'Amore 	audigyls_del_controls(dev);
5984a20ca87SGarrett D'Amore 
59948722b5fSGarrett D'Amore 	if (dev->adev != NULL) {
60048722b5fSGarrett D'Amore 		audio_dev_free(dev->adev);
60148722b5fSGarrett D'Amore 	}
60248722b5fSGarrett D'Amore 	if (dev->regsh != NULL) {
60348722b5fSGarrett D'Amore 		ddi_regs_map_free(&dev->regsh);
60448722b5fSGarrett D'Amore 	}
60548722b5fSGarrett D'Amore 	if (dev->pcih != NULL) {
60648722b5fSGarrett D'Amore 		pci_config_teardown(&dev->pcih);
60748722b5fSGarrett D'Amore 	}
60848722b5fSGarrett D'Amore 	kmem_free(dev, sizeof (*dev));
60948722b5fSGarrett D'Amore }
61048722b5fSGarrett D'Amore 
61148722b5fSGarrett D'Amore void
audigyls_hwinit(audigyls_dev_t * dev)61248722b5fSGarrett D'Amore audigyls_hwinit(audigyls_dev_t *dev)
61348722b5fSGarrett D'Amore {
61448722b5fSGarrett D'Amore 	static unsigned int spi_dac[] = {
61548722b5fSGarrett D'Amore 		0x00ff, 0x02ff, 0x0400, 0x520, 0x0620, 0x08ff, 0x0aff, 0x0cff,
61648722b5fSGarrett D'Amore 		0x0eff, 0x10ff, 0x1200, 0x1400, 0x1800, 0x1aff, 0x1cff,
61748722b5fSGarrett D'Amore 		0x1e00, 0x0530, 0x0602, 0x0622, 0x1400,
61848722b5fSGarrett D'Amore 	};
61948722b5fSGarrett D'Amore 
62048722b5fSGarrett D'Amore 	uint32_t	tmp;
62148722b5fSGarrett D'Amore 	int		i, tries;
62248722b5fSGarrett D'Amore 	uint32_t	paddr;
62348722b5fSGarrett D'Amore 	uint32_t	chunksz;
62448722b5fSGarrett D'Amore 	audigyls_port_t	*port;
62548722b5fSGarrett D'Amore 
62648722b5fSGarrett D'Amore 
62748722b5fSGarrett D'Amore 	/* Set the orange jack to be analog out or S/PDIF */
62848722b5fSGarrett D'Amore 	select_digital_enable(dev, dev->digital_enable);
62948722b5fSGarrett D'Amore 
63048722b5fSGarrett D'Amore 	/*
63148722b5fSGarrett D'Amore 	 * In P17, there's 8 GPIO pins.
63248722b5fSGarrett D'Amore 	 * GPIO register: 0x00XXYYZZ
63348722b5fSGarrett D'Amore 	 * XX: Configure GPIO to be either GPI (0) or GPO (1).
63448722b5fSGarrett D'Amore 	 * YY: GPO values, applicable if the pin is configure to be GPO.
63548722b5fSGarrett D'Amore 	 * ZZ: GPI values, applicable if the pin is configure to be GPI.
63648722b5fSGarrett D'Amore 	 *
63748722b5fSGarrett D'Amore 	 * in SB570, pin 0-4 and 6 is used as GPO and pin 5 and 7 is
63848722b5fSGarrett D'Amore 	 * used as GPI.
63948722b5fSGarrett D'Amore 	 *
64048722b5fSGarrett D'Amore 	 * GPO0:
64148722b5fSGarrett D'Amore 	 * 1 ==> Analog output
64248722b5fSGarrett D'Amore 	 * 0 ==> Digital output
64348722b5fSGarrett D'Amore 	 * GPO1:
64448722b5fSGarrett D'Amore 	 * 1 ==> Enable output on card
64548722b5fSGarrett D'Amore 	 * 0 ==> Disable output on card
64648722b5fSGarrett D'Amore 	 * GPO2:
64748722b5fSGarrett D'Amore 	 * 1 ==> Enable Mic Bias and Mic Path
64848722b5fSGarrett D'Amore 	 * 0 ==> Disable Mic Bias and Mic Path
64948722b5fSGarrett D'Amore 	 * GPO3:
65048722b5fSGarrett D'Amore 	 * 1 ==> Disable SPDIF-IO output
65148722b5fSGarrett D'Amore 	 * 0 ==> Enable SPDIF-IO output
65248722b5fSGarrett D'Amore 	 * GPO4 and GPO6:
65348722b5fSGarrett D'Amore 	 * DAC sampling rate selection:
65448722b5fSGarrett D'Amore 	 * Not applicable to SB570 since DAC is controlled through SPI
65548722b5fSGarrett D'Amore 	 * GPI5:
65648722b5fSGarrett D'Amore 	 * 1 ==> Front Panel is not connected
65748722b5fSGarrett D'Amore 	 * 0 ==> Front Panel is connected
65848722b5fSGarrett D'Amore 	 * GPI7:
65948722b5fSGarrett D'Amore 	 * 1 ==> Front Panel Headphone is not connected
66048722b5fSGarrett D'Amore 	 * 0 ==> Front Panel Headphone is connected
66148722b5fSGarrett D'Amore 	 */
66248722b5fSGarrett D'Amore 	if (dev->ac97)
66348722b5fSGarrett D'Amore 		OUTL(dev, GPIO, 0x005f03a3);
66448722b5fSGarrett D'Amore 	else {
66548722b5fSGarrett D'Amore 		/* for SBLive 7.1 */
66648722b5fSGarrett D'Amore 		OUTL(dev, GPIO, 0x005f4301);
66748722b5fSGarrett D'Amore 
66848722b5fSGarrett D'Amore 		audigyls_i2c_write(dev, 0x15, 0x2);
66948722b5fSGarrett D'Amore 		tries = 0;
67048722b5fSGarrett D'Amore 	again:
671*2a7bf89eSGarrett D'Amore 		for (i = 0; i < (sizeof (spi_dac) / sizeof (spi_dac[0])); i++) {
67248722b5fSGarrett D'Amore 			if (!audigyls_spi_write(dev, spi_dac[i]) &&
67348722b5fSGarrett D'Amore 			    tries < 100) {
67448722b5fSGarrett D'Amore 				tries++;
67548722b5fSGarrett D'Amore 				goto again;
67648722b5fSGarrett D'Amore 			}
67748722b5fSGarrett D'Amore 		}
67848722b5fSGarrett D'Amore 	}
67948722b5fSGarrett D'Amore 
68068c47f65SGarrett D'Amore 	OUTL(dev, IER, 0);
68148722b5fSGarrett D'Amore 	OUTL(dev, HC, 0x00000009);	/* Enable audio, use 48 kHz */
68248722b5fSGarrett D'Amore 
68348722b5fSGarrett D'Amore 	tmp = read_chan(dev, SRCTL, 0);
68448722b5fSGarrett D'Amore 	if (dev->ac97)
68548722b5fSGarrett D'Amore 		tmp |= 0xf0c81000;	/* Record src0/src1 from ac97 */
68648722b5fSGarrett D'Amore 	else
68748722b5fSGarrett D'Amore 		tmp |= 0x50c81000;	/* Record src0/src1 from I2SIN */
68848722b5fSGarrett D'Amore 	tmp &= ~0x0303c00f;		/* Set sample rates to 48 kHz */
68948722b5fSGarrett D'Amore 	write_chan(dev, SRCTL, 0, tmp);
69048722b5fSGarrett D'Amore 
69148722b5fSGarrett D'Amore 	write_reg(dev, HMIXMAP_I2S, 0x76543210);	/* Default out route */
69248722b5fSGarrett D'Amore 	write_reg(dev, AUDCTL, 0x0f0f003f);	/* Enable all outputs */
69348722b5fSGarrett D'Amore 
69448722b5fSGarrett D'Amore 	/* All audio stopped! */
69548722b5fSGarrett D'Amore 	write_reg(dev, SA, 0);
69648722b5fSGarrett D'Amore 
69748722b5fSGarrett D'Amore 	for (i = 0; i < 4; i++) {
69848722b5fSGarrett D'Amore 		/*
69948722b5fSGarrett D'Amore 		 * Reset DMA pointers and counters.  Note that we do
70048722b5fSGarrett D'Amore 		 * not use scatter/gather.
70148722b5fSGarrett D'Amore 		 */
70248722b5fSGarrett D'Amore 		write_chan(dev, PTBA, i, 0);
70348722b5fSGarrett D'Amore 		write_chan(dev, PTBS, i, 0);
70448722b5fSGarrett D'Amore 		write_chan(dev, PTCA, i, 0);
70548722b5fSGarrett D'Amore 
70648722b5fSGarrett D'Amore 		write_chan(dev, CPFA, i, 0);
70748722b5fSGarrett D'Amore 		write_chan(dev, PFEA, i, 0);
70848722b5fSGarrett D'Amore 		write_chan(dev, CPCAV, i, 0);
70948722b5fSGarrett D'Amore 
71048722b5fSGarrett D'Amore 		write_chan(dev, CRFA, i, 0);
71148722b5fSGarrett D'Amore 		write_chan(dev, CRCAV, i, 0);
71248722b5fSGarrett D'Amore 	}
71348722b5fSGarrett D'Amore 
71448722b5fSGarrett D'Amore 	/*
71548722b5fSGarrett D'Amore 	 * The 5.1 play port made up channels 0, 1, and 3.  The record
71648722b5fSGarrett D'Amore 	 * port is channel 2.
71748722b5fSGarrett D'Amore 	 */
71848722b5fSGarrett D'Amore 	port = dev->port[AUDIGYLS_PLAY_PORT];
71948722b5fSGarrett D'Amore 	paddr = port->buf_paddr;
72048722b5fSGarrett D'Amore 	chunksz = port->buf_frames * 4;
72148722b5fSGarrett D'Amore 	write_chan(dev, PFBA, 0, paddr);
72248722b5fSGarrett D'Amore 	write_chan(dev, PFBS, 0, chunksz << 16);
72348722b5fSGarrett D'Amore 	paddr += chunksz;
72448722b5fSGarrett D'Amore 	write_chan(dev, PFBA, 1, paddr);
72548722b5fSGarrett D'Amore 	write_chan(dev, PFBS, 1, chunksz << 16);
72648722b5fSGarrett D'Amore 	paddr += chunksz;
72748722b5fSGarrett D'Amore 	write_chan(dev, PFBA, 3, paddr);
72848722b5fSGarrett D'Amore 	write_chan(dev, PFBS, 3, chunksz << 16);
72948722b5fSGarrett D'Amore 
73048722b5fSGarrett D'Amore 	/* Record */
73148722b5fSGarrett D'Amore 	port = dev->port[AUDIGYLS_REC_PORT];
73248722b5fSGarrett D'Amore 	paddr = port->buf_paddr;
73348722b5fSGarrett D'Amore 	chunksz = port->buf_frames * 4;
73448722b5fSGarrett D'Amore 	write_chan(dev, RFBA, 2, paddr);
73548722b5fSGarrett D'Amore 	write_chan(dev, RFBS, 2, chunksz << 16);
73648722b5fSGarrett D'Amore 
73748722b5fSGarrett D'Amore 	/* Set sample rates to 48 kHz. */
73848722b5fSGarrett D'Amore 	tmp = read_chan(dev, SRCTL, 0) & ~0x0303c00f;
73948722b5fSGarrett D'Amore 	write_chan(dev, SRCTL, 0, tmp);
74048722b5fSGarrett D'Amore 
74148722b5fSGarrett D'Amore 	write_reg(dev, SCS0, 0x02108004);	/* Audio */
74248722b5fSGarrett D'Amore 	write_reg(dev, SCS1, 0x02108004);	/* Audio */
74348722b5fSGarrett D'Amore 	write_reg(dev, SCS2, 0x02108004);	/* Audio */
74448722b5fSGarrett D'Amore 	write_reg(dev, SCS3, 0x02108004);	/* Audio */
74548722b5fSGarrett D'Amore }
74648722b5fSGarrett D'Amore 
74748722b5fSGarrett D'Amore #define	PLAYCTL	(AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_PLAY)
74848722b5fSGarrett D'Amore #define	RECCTL	(AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_REC)
74948722b5fSGarrett D'Amore #define	MONCTL	(AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_MONITOR)
75048722b5fSGarrett D'Amore #define	PCMVOL	(PLAYCTL | AUDIO_CTRL_FLAG_PCMVOL)
75148722b5fSGarrett D'Amore #define	MAINVOL	(PLAYCTL | AUDIO_CTRL_FLAG_MAINVOL)
75248722b5fSGarrett D'Amore #define	RECVOL	(RECCTL | AUDIO_CTRL_FLAG_RECVOL)
75348722b5fSGarrett D'Amore #define	MONVOL	(MONCTL | AUDIO_CTRL_FLAG_MONVOL)
75448722b5fSGarrett D'Amore 
75548722b5fSGarrett D'Amore #define	MASK(nbits)	((1 << (nbits)) - 1)
75648722b5fSGarrett D'Amore #define	SCALE(val, nbits)	\
75748722b5fSGarrett D'Amore 	((uint8_t)((((val) * MASK(nbits)) / 100)) << (8 - (nbits)))
75848722b5fSGarrett D'Amore 
75948722b5fSGarrett D'Amore static uint32_t
audigyls_stereo_scale(uint32_t value,uint8_t bits)76048722b5fSGarrett D'Amore audigyls_stereo_scale(uint32_t value, uint8_t bits)
76148722b5fSGarrett D'Amore {
76248722b5fSGarrett D'Amore 	uint8_t			left, right;
76348722b5fSGarrett D'Amore 	uint32_t		val;
76448722b5fSGarrett D'Amore 
76548722b5fSGarrett D'Amore 	left = (value >> 8) & 0xff;
76648722b5fSGarrett D'Amore 	right = value & 0xff;
76748722b5fSGarrett D'Amore 
76848722b5fSGarrett D'Amore 	val = (((left * ((1 << bits) - 1) / 100) << 8) |
76948722b5fSGarrett D'Amore 	    (right * ((1 << bits) - 1) / 100));
77048722b5fSGarrett D'Amore 	return (val);
77148722b5fSGarrett D'Amore }
77248722b5fSGarrett D'Amore 
77348722b5fSGarrett D'Amore static void
audigyls_configure_mixer(audigyls_dev_t * dev)77448722b5fSGarrett D'Amore audigyls_configure_mixer(audigyls_dev_t *dev)
77548722b5fSGarrett D'Amore {
77648722b5fSGarrett D'Amore 	unsigned int	r, v1, v2;
77748722b5fSGarrett D'Amore 
77848722b5fSGarrett D'Amore 	/* output items */
77948722b5fSGarrett D'Amore 	/* front */
78048722b5fSGarrett D'Amore 	r = 0xffff - audigyls_stereo_scale(dev->controls[CTL_FRONT].val, 8);
78148722b5fSGarrett D'Amore 	r = (r << 16) | r;
78248722b5fSGarrett D'Amore 	write_chan(dev, MIXVOL_I2S, 0, r);
78348722b5fSGarrett D'Amore 
78448722b5fSGarrett D'Amore 	/* surround */
78548722b5fSGarrett D'Amore 	r = 0xffff - audigyls_stereo_scale(dev->controls[CTL_SURROUND].val, 8);
78648722b5fSGarrett D'Amore 	r = (r << 16) | r;
78748722b5fSGarrett D'Amore 	write_chan(dev, MIXVOL_I2S, 3, r);
78848722b5fSGarrett D'Amore 
78948722b5fSGarrett D'Amore 	/* center/lfe */
79048722b5fSGarrett D'Amore 	v1 = 255 - SCALE(dev->controls[CTL_CENTER].val, 8);
79148722b5fSGarrett D'Amore 	v2 = 255 - SCALE(dev->controls[CTL_LFE].val, 8);
79248722b5fSGarrett D'Amore 	r = (v1 << 8) | v2;
79348722b5fSGarrett D'Amore 	r = (r << 16) | r;
79448722b5fSGarrett D'Amore 	write_chan(dev, MIXVOL_I2S, 1, r);
79548722b5fSGarrett D'Amore 
79648722b5fSGarrett D'Amore 	/* spread */
79748722b5fSGarrett D'Amore 	r = dev->controls[CTL_SPREAD].val ? 0x10101010 : 0x76543210;
79848722b5fSGarrett D'Amore 	write_reg(dev, HMIXMAP_I2S, r);
79948722b5fSGarrett D'Amore 
80048722b5fSGarrett D'Amore 	/* input items */
80148722b5fSGarrett D'Amore 
80248722b5fSGarrett D'Amore 	/* recgain */
80348722b5fSGarrett D'Amore 	v1 = dev->controls[CTL_RECORDVOL].val;
80448722b5fSGarrett D'Amore 	if (dev->ac97_recgain && !dev->controls[CTL_LOOP].val) {
80548722b5fSGarrett D'Amore 		/*
80648722b5fSGarrett D'Amore 		 * For AC'97, we use the AC'97 record gain, unless we are
80748722b5fSGarrett D'Amore 		 * in loopback.
80848722b5fSGarrett D'Amore 		 */
809a60cc674SGarrett D'Amore 		(void) ac97_control_set(dev->ac97_recgain, v1);
81048722b5fSGarrett D'Amore 		write_reg(dev, P17RECVOLL, 0x30303030);
81148722b5fSGarrett D'Amore 		write_reg(dev, P17RECVOLH, 0x30303030);
81248722b5fSGarrett D'Amore 	} else {
81348722b5fSGarrett D'Amore 		/*
81448722b5fSGarrett D'Amore 		 * Otherwise we set the P17 gain.
81548722b5fSGarrett D'Amore 		 */
81648722b5fSGarrett D'Amore 		r = 0xffff - audigyls_stereo_scale(v1, 8);
81748722b5fSGarrett D'Amore 		r = r << 16 | r;
81848722b5fSGarrett D'Amore 		write_reg(dev, P17RECVOLL, r);
81948722b5fSGarrett D'Amore 		write_reg(dev, P17RECVOLH, r);
82048722b5fSGarrett D'Amore 	}
82148722b5fSGarrett D'Amore 
82248722b5fSGarrett D'Amore 	/* monitor gain */
82348722b5fSGarrett D'Amore 	if (dev->ac97) {
82448722b5fSGarrett D'Amore 		/* AC'97 monitor gain is done by the AC'97 codec */
82548722b5fSGarrett D'Amore 		write_chan(dev, SRCTL, 1, 0x30303030);
82648722b5fSGarrett D'Amore 		write_reg(dev, SMIXMAP_I2S, 0x10101076);
82748722b5fSGarrett D'Amore 	} else {
82848722b5fSGarrett D'Amore 		/* For non-AC'97 devices, just a single master monitor gain */
82948722b5fSGarrett D'Amore 		r = 255 - SCALE(dev->controls[CTL_MONGAIN].val, 8);
83048722b5fSGarrett D'Amore 		write_chan(dev, SRCTL, 1, 0xffff0000 | r << 8 | r);
83148722b5fSGarrett D'Amore 		if (r != 0xff) {
83248722b5fSGarrett D'Amore 			write_reg(dev, SMIXMAP_I2S, 0x10101076);
83348722b5fSGarrett D'Amore 		} else {
83448722b5fSGarrett D'Amore 			write_reg(dev, SMIXMAP_I2S, 0x10101010);
83548722b5fSGarrett D'Amore 		}
83648722b5fSGarrett D'Amore 	}
83748722b5fSGarrett D'Amore 
83848722b5fSGarrett D'Amore 	/* record source */
83948722b5fSGarrett D'Amore 	if (dev->ac97_recsrc != NULL) {
840a60cc674SGarrett D'Amore 		(void) ac97_control_set(dev->ac97_recsrc,
84148722b5fSGarrett D'Amore 		    dev->controls[CTL_RECSRC].val);
84248722b5fSGarrett D'Amore 		v1 = RECSEL_AC97;	/* Audigy LS */
84348722b5fSGarrett D'Amore 	} else {
84448722b5fSGarrett D'Amore 		switch (dev->controls[CTL_RECSRC].val) {
84548722b5fSGarrett D'Amore 		case 1:
84648722b5fSGarrett D'Amore 			audigyls_i2c_write(dev, 0x15, 0x2);   /* Mic */
84748722b5fSGarrett D'Amore 			OUTL(dev, GPIO, INL(dev, GPIO) | 0x400);
84848722b5fSGarrett D'Amore 			break;
84948722b5fSGarrett D'Amore 
85048722b5fSGarrett D'Amore 		case 2:
85148722b5fSGarrett D'Amore 			audigyls_i2c_write(dev, 0x15, 0x4);   /* Line */
85248722b5fSGarrett D'Amore 			OUTL(dev, GPIO, INL(dev, GPIO) & ~0x400);
85348722b5fSGarrett D'Amore 			break;
85448722b5fSGarrett D'Amore 		}
85548722b5fSGarrett D'Amore 		v1 = RECSEL_I2SIN;	/* SB 7.1 value */
85648722b5fSGarrett D'Amore 	}
85748722b5fSGarrett D'Amore 
85848722b5fSGarrett D'Amore 	/* If loopback, record what you hear instead */
85948722b5fSGarrett D'Amore 
86048722b5fSGarrett D'Amore 	if (dev->controls[CTL_LOOP].val) {
86148722b5fSGarrett D'Amore 		r = 0;
86248722b5fSGarrett D'Amore 		v1 = RECSEL_I2SOUT;
86368c47f65SGarrett D'Amore 		r |= (v1 << 28) | (v1 << 24) | (v1 << 20) | (v1 << 16) | v1;
86448722b5fSGarrett D'Amore 	} else {
86548722b5fSGarrett D'Amore 		/*
86648722b5fSGarrett D'Amore 		 * You'd think this would be the same as the logic
86748722b5fSGarrett D'Amore 		 * above, but experience shows that what you need for
86848722b5fSGarrett D'Amore 		 * loopback is different.  This whole thing looks
86948722b5fSGarrett D'Amore 		 * particularly fishy to me.  I suspect someone has
87048722b5fSGarrett D'Amore 		 * made a mistake somewhere.  But I can't seem to
87148722b5fSGarrett D'Amore 		 * figure out where it lies.
87248722b5fSGarrett D'Amore 		 */
873e7437094SGarrett D'Amore 		if (dev->ac97_recsrc != NULL) {
874e7437094SGarrett D'Amore 			r = 0xe4;
875e7437094SGarrett D'Amore 			for (int i = 0; i < 4; i++)
876e7437094SGarrett D'Amore 				r |= v1 << (16 + i * 3); /* Select input */
877e7437094SGarrett D'Amore 		} else {
878e7437094SGarrett D'Amore 			r = (v1 << 28) | (v1 << 24) | (v1 << 20) | (v1 << 16) |
879e7437094SGarrett D'Amore 			    v1;
880e7437094SGarrett D'Amore 		}
88148722b5fSGarrett D'Amore 	}
88248722b5fSGarrett D'Amore 
88348722b5fSGarrett D'Amore 	write_reg(dev, P17RECSEL, r);
88448722b5fSGarrett D'Amore }
88548722b5fSGarrett D'Amore 
88648722b5fSGarrett D'Amore static int
audigyls_set_control(void * arg,uint64_t val)88748722b5fSGarrett D'Amore audigyls_set_control(void *arg, uint64_t val)
88848722b5fSGarrett D'Amore {
88948722b5fSGarrett D'Amore 	audigyls_ctrl_t	*pc = arg;
89048722b5fSGarrett D'Amore 	audigyls_dev_t	*dev = pc->dev;
89148722b5fSGarrett D'Amore 
89248722b5fSGarrett D'Amore 	switch (pc->num) {
89348722b5fSGarrett D'Amore 
89448722b5fSGarrett D'Amore 	case CTL_FRONT:
89548722b5fSGarrett D'Amore 	case CTL_SURROUND:
89648722b5fSGarrett D'Amore 	case CTL_RECORDVOL:
89748722b5fSGarrett D'Amore 		if (((val & 0xff) > 100) ||
89848722b5fSGarrett D'Amore 		    (((val & 0xff00) >> 8) > 100) ||
89948722b5fSGarrett D'Amore 		    ((val & ~0xffff) != 0)) {
90048722b5fSGarrett D'Amore 			return (EINVAL);
90148722b5fSGarrett D'Amore 		}
90248722b5fSGarrett D'Amore 		break;
90348722b5fSGarrett D'Amore 
90448722b5fSGarrett D'Amore 	case CTL_CENTER:
90548722b5fSGarrett D'Amore 	case CTL_LFE:
90648722b5fSGarrett D'Amore 	case CTL_MONGAIN:
90748722b5fSGarrett D'Amore 		if (val > 100) {
90848722b5fSGarrett D'Amore 			return (EINVAL);
90948722b5fSGarrett D'Amore 		}
91048722b5fSGarrett D'Amore 		break;
91148722b5fSGarrett D'Amore 
91248722b5fSGarrett D'Amore 	case CTL_RECSRC:
91348722b5fSGarrett D'Amore 		if (((1U << val) & (dev->recmask)) == 0) {
91448722b5fSGarrett D'Amore 			return (EINVAL);
91548722b5fSGarrett D'Amore 		}
91648722b5fSGarrett D'Amore 		break;
91748722b5fSGarrett D'Amore 
91848722b5fSGarrett D'Amore 	case CTL_SPREAD:
91948722b5fSGarrett D'Amore 	case CTL_LOOP:
92048722b5fSGarrett D'Amore 		switch (val) {
92148722b5fSGarrett D'Amore 		case 0:
92248722b5fSGarrett D'Amore 		case 1:
92348722b5fSGarrett D'Amore 			break;
92448722b5fSGarrett D'Amore 		default:
92548722b5fSGarrett D'Amore 			return (EINVAL);
92648722b5fSGarrett D'Amore 		}
92748722b5fSGarrett D'Amore 	}
92848722b5fSGarrett D'Amore 
92948722b5fSGarrett D'Amore 	mutex_enter(&dev->mutex);
93048722b5fSGarrett D'Amore 	pc->val = val;
93168c47f65SGarrett D'Amore 	audigyls_configure_mixer(dev);
93268c47f65SGarrett D'Amore 
93348722b5fSGarrett D'Amore 	mutex_exit(&dev->mutex);
93448722b5fSGarrett D'Amore 
93548722b5fSGarrett D'Amore 	return (0);
93648722b5fSGarrett D'Amore }
93748722b5fSGarrett D'Amore 
93848722b5fSGarrett D'Amore static int
audigyls_get_control(void * arg,uint64_t * val)93948722b5fSGarrett D'Amore audigyls_get_control(void *arg, uint64_t *val)
94048722b5fSGarrett D'Amore {
94148722b5fSGarrett D'Amore 	audigyls_ctrl_t	*pc = arg;
94248722b5fSGarrett D'Amore 	audigyls_dev_t	*dev = pc->dev;
94348722b5fSGarrett D'Amore 
94448722b5fSGarrett D'Amore 	mutex_enter(&dev->mutex);
94548722b5fSGarrett D'Amore 	*val = pc->val;
94648722b5fSGarrett D'Amore 	mutex_exit(&dev->mutex);
94748722b5fSGarrett D'Amore 	return (0);
94848722b5fSGarrett D'Amore }
94948722b5fSGarrett D'Amore 
95048722b5fSGarrett D'Amore static void
audigyls_alloc_ctrl(audigyls_dev_t * dev,uint32_t num,uint64_t val)95148722b5fSGarrett D'Amore audigyls_alloc_ctrl(audigyls_dev_t *dev, uint32_t num, uint64_t val)
95248722b5fSGarrett D'Amore {
95348722b5fSGarrett D'Amore 	audio_ctrl_desc_t	desc;
95448722b5fSGarrett D'Amore 	audigyls_ctrl_t		*pc;
95548722b5fSGarrett D'Amore 
95648722b5fSGarrett D'Amore 	bzero(&desc, sizeof (desc));
95748722b5fSGarrett D'Amore 
95848722b5fSGarrett D'Amore 	pc = &dev->controls[num];
95948722b5fSGarrett D'Amore 	pc->num = num;
96048722b5fSGarrett D'Amore 	pc->dev = dev;
96148722b5fSGarrett D'Amore 
96248722b5fSGarrett D'Amore 
96348722b5fSGarrett D'Amore 	switch (num) {
96448722b5fSGarrett D'Amore 	case CTL_FRONT:
96548722b5fSGarrett D'Amore 		desc.acd_name = AUDIO_CTRL_ID_FRONT;
96648722b5fSGarrett D'Amore 		desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
96748722b5fSGarrett D'Amore 		desc.acd_minvalue = 0;
96848722b5fSGarrett D'Amore 		desc.acd_maxvalue = 100;
96948722b5fSGarrett D'Amore 		desc.acd_flags = MAINVOL;
97048722b5fSGarrett D'Amore 		break;
97148722b5fSGarrett D'Amore 
97248722b5fSGarrett D'Amore 	case CTL_SURROUND:
97348722b5fSGarrett D'Amore 		desc.acd_name = AUDIO_CTRL_ID_SURROUND;
97448722b5fSGarrett D'Amore 		desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
97548722b5fSGarrett D'Amore 		desc.acd_minvalue = 0;
97648722b5fSGarrett D'Amore 		desc.acd_maxvalue = 100;
97748722b5fSGarrett D'Amore 		desc.acd_flags = MAINVOL;
97848722b5fSGarrett D'Amore 		break;
97948722b5fSGarrett D'Amore 
98048722b5fSGarrett D'Amore 	case CTL_CENTER:
98148722b5fSGarrett D'Amore 		desc.acd_name = AUDIO_CTRL_ID_CENTER;
98248722b5fSGarrett D'Amore 		desc.acd_type = AUDIO_CTRL_TYPE_MONO;
98348722b5fSGarrett D'Amore 		desc.acd_minvalue = 0;
98448722b5fSGarrett D'Amore 		desc.acd_maxvalue = 100;
98548722b5fSGarrett D'Amore 		desc.acd_flags = MAINVOL;
98648722b5fSGarrett D'Amore 		break;
98748722b5fSGarrett D'Amore 
98848722b5fSGarrett D'Amore 	case CTL_LFE:
98948722b5fSGarrett D'Amore 		desc.acd_name = AUDIO_CTRL_ID_LFE;
99048722b5fSGarrett D'Amore 		desc.acd_type = AUDIO_CTRL_TYPE_MONO;
99148722b5fSGarrett D'Amore 		desc.acd_minvalue = 0;
99248722b5fSGarrett D'Amore 		desc.acd_maxvalue = 100;
99348722b5fSGarrett D'Amore 		desc.acd_flags = MAINVOL;
99448722b5fSGarrett D'Amore 		break;
99548722b5fSGarrett D'Amore 
99648722b5fSGarrett D'Amore 	case CTL_RECORDVOL:
99748722b5fSGarrett D'Amore 		desc.acd_name = AUDIO_CTRL_ID_RECGAIN;
99848722b5fSGarrett D'Amore 		desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
99948722b5fSGarrett D'Amore 		desc.acd_minvalue = 0;
100048722b5fSGarrett D'Amore 		desc.acd_maxvalue = 100;
100148722b5fSGarrett D'Amore 		desc.acd_flags = RECVOL;
100248722b5fSGarrett D'Amore 		break;
100348722b5fSGarrett D'Amore 
100448722b5fSGarrett D'Amore 	case CTL_RECSRC:
100548722b5fSGarrett D'Amore 		desc.acd_name = AUDIO_CTRL_ID_RECSRC;
100648722b5fSGarrett D'Amore 		desc.acd_type = AUDIO_CTRL_TYPE_ENUM;
100748722b5fSGarrett D'Amore 		desc.acd_flags = RECCTL;
100848722b5fSGarrett D'Amore 
100948722b5fSGarrett D'Amore 		/*
101048722b5fSGarrett D'Amore 		 * For AC'97 devices, we want to expose the reasonable
101148722b5fSGarrett D'Amore 		 * AC'97 input sources, but suppress the stereomix,
101248722b5fSGarrett D'Amore 		 * because we use loopback instead.
101348722b5fSGarrett D'Amore 		 */
101448722b5fSGarrett D'Amore 		if (dev->ac97_recsrc) {
101548722b5fSGarrett D'Amore 			int i, j;
101648722b5fSGarrett D'Amore 			const char *n;
101748722b5fSGarrett D'Amore 			const audio_ctrl_desc_t *adp;
101848722b5fSGarrett D'Amore 
101948722b5fSGarrett D'Amore 			adp = ac97_control_desc(dev->ac97_recsrc);
102048722b5fSGarrett D'Amore 			for (i = 0; i < 64; i++) {
102148722b5fSGarrett D'Amore 				n = adp->acd_enum[i];
102248722b5fSGarrett D'Amore 
102348722b5fSGarrett D'Amore 				if (((adp->acd_minvalue & (1 << i)) == 0) ||
102448722b5fSGarrett D'Amore 				    (n == NULL)) {
102548722b5fSGarrett D'Amore 					continue;
102648722b5fSGarrett D'Amore 				}
102748722b5fSGarrett D'Amore 				for (j = 0; audigyls_badsrcs[j]; j++) {
102848722b5fSGarrett D'Amore 					if (strcmp(n, audigyls_badsrcs[j])
102948722b5fSGarrett D'Amore 					    == 0) {
103048722b5fSGarrett D'Amore 						n = NULL;
103148722b5fSGarrett D'Amore 						break;
103248722b5fSGarrett D'Amore 					}
103348722b5fSGarrett D'Amore 				}
103448722b5fSGarrett D'Amore 				if (n) {
103548722b5fSGarrett D'Amore 					desc.acd_enum[i] = n;
103648722b5fSGarrett D'Amore 					dev->recmask |= (1 << i);
103748722b5fSGarrett D'Amore 				}
103848722b5fSGarrett D'Amore 			}
103948722b5fSGarrett D'Amore 			desc.acd_minvalue = desc.acd_maxvalue = dev->recmask;
104048722b5fSGarrett D'Amore 		} else {
104148722b5fSGarrett D'Amore 			dev->recmask = 3;
104248722b5fSGarrett D'Amore 			desc.acd_minvalue = 3;
104348722b5fSGarrett D'Amore 			desc.acd_maxvalue = 3;
104448722b5fSGarrett D'Amore 			desc.acd_enum[0] = AUDIO_PORT_MIC;
104548722b5fSGarrett D'Amore 			desc.acd_enum[1] = AUDIO_PORT_LINEIN;
104648722b5fSGarrett D'Amore 		}
104748722b5fSGarrett D'Amore 		break;
104848722b5fSGarrett D'Amore 
104948722b5fSGarrett D'Amore 	case CTL_MONGAIN:
105048722b5fSGarrett D'Amore 		ASSERT(!dev->ac97);
105148722b5fSGarrett D'Amore 		desc.acd_name = AUDIO_CTRL_ID_MONGAIN;
105248722b5fSGarrett D'Amore 		desc.acd_type = AUDIO_CTRL_TYPE_MONO;
105348722b5fSGarrett D'Amore 		desc.acd_minvalue = 0;
105448722b5fSGarrett D'Amore 		desc.acd_maxvalue = 100;
105548722b5fSGarrett D'Amore 		desc.acd_flags = MONVOL;
105648722b5fSGarrett D'Amore 		break;
105748722b5fSGarrett D'Amore 
105848722b5fSGarrett D'Amore 	case CTL_SPREAD:
105948722b5fSGarrett D'Amore 		desc.acd_name = AUDIO_CTRL_ID_SPREAD;
106048722b5fSGarrett D'Amore 		desc.acd_type = AUDIO_CTRL_TYPE_BOOLEAN;
106148722b5fSGarrett D'Amore 		desc.acd_minvalue = 0;
106248722b5fSGarrett D'Amore 		desc.acd_maxvalue = 1;
106348722b5fSGarrett D'Amore 		desc.acd_flags = PLAYCTL;
106448722b5fSGarrett D'Amore 		break;
106548722b5fSGarrett D'Amore 
106648722b5fSGarrett D'Amore 	case CTL_LOOP:
106748722b5fSGarrett D'Amore 		desc.acd_name = AUDIO_CTRL_ID_LOOPBACK;
106848722b5fSGarrett D'Amore 		desc.acd_type = AUDIO_CTRL_TYPE_BOOLEAN;
106948722b5fSGarrett D'Amore 		desc.acd_minvalue = 0;
107048722b5fSGarrett D'Amore 		desc.acd_maxvalue = 1;
107148722b5fSGarrett D'Amore 		desc.acd_flags = RECCTL;
107248722b5fSGarrett D'Amore 		break;
107348722b5fSGarrett D'Amore 	}
107448722b5fSGarrett D'Amore 
107548722b5fSGarrett D'Amore 	pc->val = val;
107648722b5fSGarrett D'Amore 	pc->ctrl = audio_dev_add_control(dev->adev, &desc,
107748722b5fSGarrett D'Amore 	    audigyls_get_control, audigyls_set_control, pc);
107848722b5fSGarrett D'Amore }
107948722b5fSGarrett D'Amore 
1080a60cc674SGarrett D'Amore static void
audigyls_add_controls(audigyls_dev_t * dev)108148722b5fSGarrett D'Amore audigyls_add_controls(audigyls_dev_t *dev)
108248722b5fSGarrett D'Amore {
10832c30fa45SGarrett D'Amore 	audio_dev_add_soft_volume(dev->adev);
108448722b5fSGarrett D'Amore 
108548722b5fSGarrett D'Amore 	audigyls_alloc_ctrl(dev, CTL_FRONT, 75 | (75 << 8));
108648722b5fSGarrett D'Amore 	audigyls_alloc_ctrl(dev, CTL_SURROUND, 75 | (75 << 8));
108748722b5fSGarrett D'Amore 	audigyls_alloc_ctrl(dev, CTL_CENTER, 75);
108848722b5fSGarrett D'Amore 	audigyls_alloc_ctrl(dev, CTL_LFE, 75);
108948722b5fSGarrett D'Amore 	audigyls_alloc_ctrl(dev, CTL_RECORDVOL, 75 | (75 << 8));
109048722b5fSGarrett D'Amore 	audigyls_alloc_ctrl(dev, CTL_RECSRC, 1);
109148722b5fSGarrett D'Amore 	audigyls_alloc_ctrl(dev, CTL_SPREAD, 0);
109248722b5fSGarrett D'Amore 	audigyls_alloc_ctrl(dev, CTL_LOOP, 0);
109348722b5fSGarrett D'Amore 	if (!dev->ac97) {
109448722b5fSGarrett D'Amore 		audigyls_alloc_ctrl(dev, CTL_MONGAIN, 0);
109548722b5fSGarrett D'Amore 	}
109648722b5fSGarrett D'Amore }
109748722b5fSGarrett D'Amore 
109848722b5fSGarrett D'Amore int
audigyls_attach(dev_info_t * dip)109948722b5fSGarrett D'Amore audigyls_attach(dev_info_t *dip)
110048722b5fSGarrett D'Amore {
110148722b5fSGarrett D'Amore 	uint16_t	pci_command, vendor, device;
110248722b5fSGarrett D'Amore 	uint32_t	subdevice;
110348722b5fSGarrett D'Amore 	audigyls_dev_t	*dev;
110448722b5fSGarrett D'Amore 	ddi_acc_handle_t pcih;
110548722b5fSGarrett D'Amore 	const char	*name, *version;
110648722b5fSGarrett D'Amore 	boolean_t	ac97 = B_FALSE;
110748722b5fSGarrett D'Amore 
110848722b5fSGarrett D'Amore 	dev = kmem_zalloc(sizeof (*dev), KM_SLEEP);
110948722b5fSGarrett D'Amore 	dev->dip = dip;
111048722b5fSGarrett D'Amore 	ddi_set_driver_private(dip, dev);
111168c47f65SGarrett D'Amore 	mutex_init(&dev->mutex, NULL, MUTEX_DRIVER, NULL);
111268c47f65SGarrett D'Amore 	mutex_init(&dev->low_mutex, NULL, MUTEX_DRIVER, NULL);
111348722b5fSGarrett D'Amore 
111448722b5fSGarrett D'Amore 	if ((dev->adev = audio_dev_alloc(dip, 0)) == NULL) {
111548722b5fSGarrett D'Amore 		cmn_err(CE_WARN, "audio_dev_alloc failed");
111648722b5fSGarrett D'Amore 		goto error;
111748722b5fSGarrett D'Amore 	}
111848722b5fSGarrett D'Amore 
111948722b5fSGarrett D'Amore 	if (pci_config_setup(dip, &pcih) != DDI_SUCCESS) {
112048722b5fSGarrett D'Amore 		audio_dev_warn(dev->adev, "pci_config_setup failed");
112148722b5fSGarrett D'Amore 		goto error;
112248722b5fSGarrett D'Amore 	}
112348722b5fSGarrett D'Amore 	dev->pcih = pcih;
112448722b5fSGarrett D'Amore 
112548722b5fSGarrett D'Amore 	vendor = pci_config_get16(pcih, PCI_CONF_VENID);
112648722b5fSGarrett D'Amore 	device = pci_config_get16(pcih, PCI_CONF_DEVID);
112748722b5fSGarrett D'Amore 	subdevice = pci_config_get16(pcih, PCI_CONF_SUBVENID);
112848722b5fSGarrett D'Amore 	subdevice <<= 16;
112948722b5fSGarrett D'Amore 	subdevice |= pci_config_get16(pcih, PCI_CONF_SUBSYSID);
113048722b5fSGarrett D'Amore 	if (vendor != PCI_VENDOR_ID_CREATIVE ||
113148722b5fSGarrett D'Amore 	    device != PCI_DEVICE_ID_CREATIVE_AUDIGYLS) {
113248722b5fSGarrett D'Amore 		audio_dev_warn(dev->adev, "Hardware not recognized "
113348722b5fSGarrett D'Amore 		    "(vendor=%x, dev=%x)", vendor, device);
113448722b5fSGarrett D'Amore 		goto error;
113548722b5fSGarrett D'Amore 	}
113648722b5fSGarrett D'Amore 
113748722b5fSGarrett D'Amore 	pci_command = pci_config_get16(pcih, PCI_CONF_COMM);
113848722b5fSGarrett D'Amore 	pci_command |= PCI_COMM_ME | PCI_COMM_IO;
113948722b5fSGarrett D'Amore 	pci_config_put16(pcih, PCI_CONF_COMM, pci_command);
114048722b5fSGarrett D'Amore 
114148722b5fSGarrett D'Amore 	if ((ddi_regs_map_setup(dip, 1, &dev->base, 0, 0, &dev_attr,
114248722b5fSGarrett D'Amore 	    &dev->regsh)) != DDI_SUCCESS) {
114348722b5fSGarrett D'Amore 		audio_dev_warn(dev->adev, "failed to map registers");
114448722b5fSGarrett D'Amore 		goto error;
114548722b5fSGarrett D'Amore 	}
114648722b5fSGarrett D'Amore 
114748722b5fSGarrett D'Amore 	/* Function of the orange jack: 0=analog, 1=digital */
114848722b5fSGarrett D'Amore 	dev->digital_enable = ddi_prop_get_int(DDI_DEV_T_ANY, dev->dip,
114948722b5fSGarrett D'Amore 	    DDI_PROP_DONTPASS, "digital-enable", 0);
115048722b5fSGarrett D'Amore 
115148722b5fSGarrett D'Amore 	switch (subdevice) {
115248722b5fSGarrett D'Amore 	case 0x11021001:	/* SB0310 */
115348722b5fSGarrett D'Amore 	case 0x11021002:	/* SB0310 */
115448722b5fSGarrett D'Amore 	case 0x11021005:	/* SB0310b */
115548722b5fSGarrett D'Amore 		name = "Creative Audigy LS";
115648722b5fSGarrett D'Amore 		version = "SB0310";	/* could also be SB0312 */
115748722b5fSGarrett D'Amore 		ac97 = B_TRUE;
115848722b5fSGarrett D'Amore 		break;
115948722b5fSGarrett D'Amore 	case 0x11021006:
116048722b5fSGarrett D'Amore 		name = "Creative Sound Blaster Live! 24 bit";
116148722b5fSGarrett D'Amore 		version = "SB0410";
116248722b5fSGarrett D'Amore 		break;
116348722b5fSGarrett D'Amore 	case 0x11021007:	/* Dell OEM version */
116448722b5fSGarrett D'Amore 		name = "Creative Sound Blaster Live! 24 bit";
116548722b5fSGarrett D'Amore 		version = "SB0413";
116648722b5fSGarrett D'Amore 		break;
116748722b5fSGarrett D'Amore 	case 0x1102100a:
116848722b5fSGarrett D'Amore 		name = "Creative Audigy SE";
116948722b5fSGarrett D'Amore 		version = "SB0570";
117048722b5fSGarrett D'Amore 		break;
117148722b5fSGarrett D'Amore 	case 0x11021011:
117248722b5fSGarrett D'Amore 		name = "Creative Audigy SE OEM";
117348722b5fSGarrett D'Amore 		version = "SB0570a";
117448722b5fSGarrett D'Amore 		break;
117548722b5fSGarrett D'Amore 	case 0x11021012:
117648722b5fSGarrett D'Amore 		name = "Creative X-Fi Extreme Audio";
117748722b5fSGarrett D'Amore 		version = "SB0790";
117848722b5fSGarrett D'Amore 		break;
117948722b5fSGarrett D'Amore 	case 0x14621009:
118048722b5fSGarrett D'Amore 		name = "MSI K8N Diamond MB";
118148722b5fSGarrett D'Amore 		version = "SB0438";
118248722b5fSGarrett D'Amore 		break;
118348722b5fSGarrett D'Amore 	case 0x12973038:
118448722b5fSGarrett D'Amore 		name = "Shuttle XPC SD31P";
118548722b5fSGarrett D'Amore 		version = "SD31P";
118648722b5fSGarrett D'Amore 		break;
118748722b5fSGarrett D'Amore 	case 0x12973041:
118848722b5fSGarrett D'Amore 		name = "Shuttle XPC SD11G5";
118948722b5fSGarrett D'Amore 		version = "SD11G5";
119048722b5fSGarrett D'Amore 		break;
119148722b5fSGarrett D'Amore 	default:
119248722b5fSGarrett D'Amore 		name = "Creative Audigy LS";
119348722b5fSGarrett D'Amore 		version = NULL;
119448722b5fSGarrett D'Amore 		break;
119548722b5fSGarrett D'Amore 	}
119648722b5fSGarrett D'Amore 
119748722b5fSGarrett D'Amore 	audio_dev_set_description(dev->adev, name);
119848722b5fSGarrett D'Amore 	if (version)
119948722b5fSGarrett D'Amore 		audio_dev_set_version(dev->adev, version);
120048722b5fSGarrett D'Amore 
120148722b5fSGarrett D'Amore 	if (ac97) {
120248722b5fSGarrett D'Amore 		ac97_ctrl_t *ctrl;
120348722b5fSGarrett D'Amore 
120448722b5fSGarrett D'Amore 		/* Original Audigy LS revision (AC97 based) */
120548722b5fSGarrett D'Amore 		dev->ac97 = ac97_allocate(dev->adev, dip,
120648722b5fSGarrett D'Amore 		    audigyls_read_ac97, audigyls_write_ac97, dev);
120748722b5fSGarrett D'Amore 		if (dev->ac97 == NULL) {
120848722b5fSGarrett D'Amore 			audio_dev_warn(dev->adev,
120948722b5fSGarrett D'Amore 			    "failed to allocate ac97 handle");
121048722b5fSGarrett D'Amore 			goto error;
121148722b5fSGarrett D'Amore 		}
121248722b5fSGarrett D'Amore 
121348722b5fSGarrett D'Amore 		ac97_probe_controls(dev->ac97);
121448722b5fSGarrett D'Amore 
121548722b5fSGarrett D'Amore 		/* remove the AC'97 controls we don't want to expose */
121648722b5fSGarrett D'Amore 		for (int i = 0; audigyls_remove_ac97[i]; i++) {
121748722b5fSGarrett D'Amore 			ctrl = ac97_control_find(dev->ac97,
121848722b5fSGarrett D'Amore 			    audigyls_remove_ac97[i]);
121948722b5fSGarrett D'Amore 			if (ctrl != NULL) {
122048722b5fSGarrett D'Amore 				ac97_control_unregister(ctrl);
122148722b5fSGarrett D'Amore 			}
122248722b5fSGarrett D'Amore 		}
122348722b5fSGarrett D'Amore 
122448722b5fSGarrett D'Amore 		dev->ac97_recgain = ac97_control_find(dev->ac97,
122548722b5fSGarrett D'Amore 		    AUDIO_CTRL_ID_RECGAIN);
122648722b5fSGarrett D'Amore 		dev->ac97_recsrc = ac97_control_find(dev->ac97,
122748722b5fSGarrett D'Amore 		    AUDIO_CTRL_ID_RECSRC);
122848722b5fSGarrett D'Amore 	}
122948722b5fSGarrett D'Amore 
123048722b5fSGarrett D'Amore 	audigyls_add_controls(dev);
123148722b5fSGarrett D'Amore 
123248722b5fSGarrett D'Amore 	if (dev->ac97) {
123348722b5fSGarrett D'Amore 		ac97_register_controls(dev->ac97);
123448722b5fSGarrett D'Amore 	}
123548722b5fSGarrett D'Amore 
123648722b5fSGarrett D'Amore 	if (audigyls_alloc_port(dev, AUDIGYLS_PLAY_PORT) != DDI_SUCCESS)
123748722b5fSGarrett D'Amore 		goto error;
123848722b5fSGarrett D'Amore 	if (audigyls_alloc_port(dev, AUDIGYLS_REC_PORT) != DDI_SUCCESS)
123948722b5fSGarrett D'Amore 		goto error;
124048722b5fSGarrett D'Amore 
124148722b5fSGarrett D'Amore 	audigyls_hwinit(dev);
124248722b5fSGarrett D'Amore 
124348722b5fSGarrett D'Amore 	audigyls_configure_mixer(dev);
124448722b5fSGarrett D'Amore 
124548722b5fSGarrett D'Amore 	if (audio_dev_register(dev->adev) != DDI_SUCCESS) {
124648722b5fSGarrett D'Amore 		audio_dev_warn(dev->adev, "unable to register with framework");
124748722b5fSGarrett D'Amore 		goto error;
124848722b5fSGarrett D'Amore 	}
124948722b5fSGarrett D'Amore 
125048722b5fSGarrett D'Amore 	ddi_report_dev(dip);
125148722b5fSGarrett D'Amore 
125248722b5fSGarrett D'Amore 	return (DDI_SUCCESS);
125348722b5fSGarrett D'Amore 
125448722b5fSGarrett D'Amore error:
125548722b5fSGarrett D'Amore 	audigyls_destroy(dev);
125648722b5fSGarrett D'Amore 	return (DDI_FAILURE);
125748722b5fSGarrett D'Amore }
125848722b5fSGarrett D'Amore 
125948722b5fSGarrett D'Amore int
audigyls_resume(dev_info_t * dip)126048722b5fSGarrett D'Amore audigyls_resume(dev_info_t *dip)
126148722b5fSGarrett D'Amore {
126248722b5fSGarrett D'Amore 	audigyls_dev_t *dev;
126348722b5fSGarrett D'Amore 
126448722b5fSGarrett D'Amore 	dev = ddi_get_driver_private(dip);
126548722b5fSGarrett D'Amore 
126648722b5fSGarrett D'Amore 	audigyls_hwinit(dev);
126748722b5fSGarrett D'Amore 
126848722b5fSGarrett D'Amore 	/* allow ac97 operations again */
126948722b5fSGarrett D'Amore 	if (dev->ac97)
127068c47f65SGarrett D'Amore 		ac97_reset(dev->ac97);
127148722b5fSGarrett D'Amore 
127268c47f65SGarrett D'Amore 	audio_dev_resume(dev->adev);
127348722b5fSGarrett D'Amore 
127448722b5fSGarrett D'Amore 	return (DDI_SUCCESS);
127548722b5fSGarrett D'Amore }
127648722b5fSGarrett D'Amore 
127748722b5fSGarrett D'Amore int
audigyls_detach(audigyls_dev_t * dev)127848722b5fSGarrett D'Amore audigyls_detach(audigyls_dev_t *dev)
127948722b5fSGarrett D'Amore {
128048722b5fSGarrett D'Amore 	if (audio_dev_unregister(dev->adev) != DDI_SUCCESS)
128148722b5fSGarrett D'Amore 		return (DDI_FAILURE);
128248722b5fSGarrett D'Amore 
128348722b5fSGarrett D'Amore 	audigyls_destroy(dev);
128448722b5fSGarrett D'Amore 	return (DDI_SUCCESS);
128548722b5fSGarrett D'Amore }
128648722b5fSGarrett D'Amore 
128748722b5fSGarrett D'Amore int
audigyls_suspend(audigyls_dev_t * dev)128848722b5fSGarrett D'Amore audigyls_suspend(audigyls_dev_t *dev)
128948722b5fSGarrett D'Amore {
129068c47f65SGarrett D'Amore 	audio_dev_suspend(dev->adev);
129148722b5fSGarrett D'Amore 
129248722b5fSGarrett D'Amore 	return (DDI_SUCCESS);
129348722b5fSGarrett D'Amore }
129448722b5fSGarrett D'Amore 
129548722b5fSGarrett D'Amore static int audigyls_ddi_attach(dev_info_t *, ddi_attach_cmd_t);
129648722b5fSGarrett D'Amore static int audigyls_ddi_detach(dev_info_t *, ddi_detach_cmd_t);
129748722b5fSGarrett D'Amore static int audigyls_ddi_quiesce(dev_info_t *);
129848722b5fSGarrett D'Amore 
129948722b5fSGarrett D'Amore static struct dev_ops audigyls_dev_ops = {
130048722b5fSGarrett D'Amore 	DEVO_REV,		/* rev */
130148722b5fSGarrett D'Amore 	0,			/* refcnt */
130248722b5fSGarrett D'Amore 	NULL,			/* getinfo */
130348722b5fSGarrett D'Amore 	nulldev,		/* identify */
130448722b5fSGarrett D'Amore 	nulldev,		/* probe */
130548722b5fSGarrett D'Amore 	audigyls_ddi_attach,	/* attach */
130648722b5fSGarrett D'Amore 	audigyls_ddi_detach,	/* detach */
130748722b5fSGarrett D'Amore 	nodev,			/* reset */
130848722b5fSGarrett D'Amore 	NULL,			/* cb_ops */
130948722b5fSGarrett D'Amore 	NULL,			/* bus_ops */
131048722b5fSGarrett D'Amore 	NULL,			/* power */
131148722b5fSGarrett D'Amore 	audigyls_ddi_quiesce,	/* quiesce */
131248722b5fSGarrett D'Amore };
131348722b5fSGarrett D'Amore 
131448722b5fSGarrett D'Amore static struct modldrv audigyls_modldrv = {
131548722b5fSGarrett D'Amore 	&mod_driverops,			/* drv_modops */
131648722b5fSGarrett D'Amore 	"Creative Audigy LS Audio",		/* linkinfo */
131748722b5fSGarrett D'Amore 	&audigyls_dev_ops,			/* dev_ops */
131848722b5fSGarrett D'Amore };
131948722b5fSGarrett D'Amore 
132048722b5fSGarrett D'Amore static struct modlinkage modlinkage = {
132148722b5fSGarrett D'Amore 	MODREV_1,
132248722b5fSGarrett D'Amore 	{ &audigyls_modldrv, NULL }
132348722b5fSGarrett D'Amore };
132448722b5fSGarrett D'Amore 
132548722b5fSGarrett D'Amore int
_init(void)132648722b5fSGarrett D'Amore _init(void)
132748722b5fSGarrett D'Amore {
132848722b5fSGarrett D'Amore 	int	rv;
132948722b5fSGarrett D'Amore 
133048722b5fSGarrett D'Amore 	audio_init_ops(&audigyls_dev_ops, AUDIGYLS_NAME);
133148722b5fSGarrett D'Amore 	if ((rv = mod_install(&modlinkage)) != 0) {
133248722b5fSGarrett D'Amore 		audio_fini_ops(&audigyls_dev_ops);
133348722b5fSGarrett D'Amore 	}
133448722b5fSGarrett D'Amore 	return (rv);
133548722b5fSGarrett D'Amore }
133648722b5fSGarrett D'Amore 
133748722b5fSGarrett D'Amore int
_fini(void)133848722b5fSGarrett D'Amore _fini(void)
133948722b5fSGarrett D'Amore {
134048722b5fSGarrett D'Amore 	int	rv;
134148722b5fSGarrett D'Amore 
134248722b5fSGarrett D'Amore 	if ((rv = mod_remove(&modlinkage)) == 0) {
134348722b5fSGarrett D'Amore 		audio_fini_ops(&audigyls_dev_ops);
134448722b5fSGarrett D'Amore 	}
134548722b5fSGarrett D'Amore 	return (rv);
134648722b5fSGarrett D'Amore }
134748722b5fSGarrett D'Amore 
134848722b5fSGarrett D'Amore int
_info(struct modinfo * modinfop)134948722b5fSGarrett D'Amore _info(struct modinfo *modinfop)
135048722b5fSGarrett D'Amore {
135148722b5fSGarrett D'Amore 	return (mod_info(&modlinkage, modinfop));
135248722b5fSGarrett D'Amore }
135348722b5fSGarrett D'Amore 
135448722b5fSGarrett D'Amore int
audigyls_ddi_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)135548722b5fSGarrett D'Amore audigyls_ddi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
135648722b5fSGarrett D'Amore {
135748722b5fSGarrett D'Amore 	switch (cmd) {
135848722b5fSGarrett D'Amore 	case DDI_ATTACH:
135948722b5fSGarrett D'Amore 		return (audigyls_attach(dip));
136048722b5fSGarrett D'Amore 
136148722b5fSGarrett D'Amore 	case DDI_RESUME:
136248722b5fSGarrett D'Amore 		return (audigyls_resume(dip));
136348722b5fSGarrett D'Amore 
136448722b5fSGarrett D'Amore 	default:
136548722b5fSGarrett D'Amore 		return (DDI_FAILURE);
136648722b5fSGarrett D'Amore 	}
136748722b5fSGarrett D'Amore }
136848722b5fSGarrett D'Amore 
136948722b5fSGarrett D'Amore int
audigyls_ddi_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)137048722b5fSGarrett D'Amore audigyls_ddi_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
137148722b5fSGarrett D'Amore {
137248722b5fSGarrett D'Amore 	audigyls_dev_t *dev;
137348722b5fSGarrett D'Amore 
137448722b5fSGarrett D'Amore 	dev = ddi_get_driver_private(dip);
137548722b5fSGarrett D'Amore 
137648722b5fSGarrett D'Amore 	switch (cmd) {
137748722b5fSGarrett D'Amore 	case DDI_DETACH:
137848722b5fSGarrett D'Amore 		return (audigyls_detach(dev));
137948722b5fSGarrett D'Amore 
138048722b5fSGarrett D'Amore 	case DDI_SUSPEND:
138148722b5fSGarrett D'Amore 		return (audigyls_suspend(dev));
138248722b5fSGarrett D'Amore 
138348722b5fSGarrett D'Amore 	default:
138448722b5fSGarrett D'Amore 		return (DDI_FAILURE);
138548722b5fSGarrett D'Amore 	}
138648722b5fSGarrett D'Amore }
138748722b5fSGarrett D'Amore 
138848722b5fSGarrett D'Amore int
audigyls_ddi_quiesce(dev_info_t * dip)138948722b5fSGarrett D'Amore audigyls_ddi_quiesce(dev_info_t *dip)
139048722b5fSGarrett D'Amore {
139148722b5fSGarrett D'Amore 	audigyls_dev_t	*dev;
139248722b5fSGarrett D'Amore 	uint32_t status;
139348722b5fSGarrett D'Amore 
139448722b5fSGarrett D'Amore 	/*
139548722b5fSGarrett D'Amore 	 * Turn off the hardware
139648722b5fSGarrett D'Amore 	 */
139768c47f65SGarrett D'Amore 	dev = ddi_get_driver_private(dip);
139848722b5fSGarrett D'Amore 
139948722b5fSGarrett D'Amore 	write_reg(dev, SA, 0);
140048722b5fSGarrett D'Amore 	OUTL(dev, IER, 0);	/* Interrupt disable */
140148722b5fSGarrett D'Amore 	write_reg(dev, AIE, 0);	/* Disable audio interrupts */
140248722b5fSGarrett D'Amore 	status = INL(dev, IPR);
140348722b5fSGarrett D'Amore 	OUTL(dev, IPR, status);	/* Acknowledge */
140448722b5fSGarrett D'Amore 	return (DDI_SUCCESS);
140548722b5fSGarrett D'Amore }
1406