17a4f122cSGarrett D'Amore /*
27a4f122cSGarrett D'Amore  * Copyright (c) 1999 Cameron Grant <cg@freebsd.org>
37a4f122cSGarrett D'Amore  *
47a4f122cSGarrett D'Amore  * Redistribution and use in source and binary forms, with or without
57a4f122cSGarrett D'Amore  * modification, are permitted provided that the following conditions
67a4f122cSGarrett D'Amore  * are met:
77a4f122cSGarrett D'Amore  * 1. Redistributions of source code must retain the above copyright
87a4f122cSGarrett D'Amore  *    notice, this list of conditions and the following disclaimer.
97a4f122cSGarrett D'Amore  * 2. Redistributions in binary form must reproduce the above copyright
107a4f122cSGarrett D'Amore  *    notice, this list of conditions and the following disclaimer in the
117a4f122cSGarrett D'Amore  *    documentation and/or other materials provided with the distribution.
127a4f122cSGarrett D'Amore  *
137a4f122cSGarrett D'Amore  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
147a4f122cSGarrett D'Amore  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
157a4f122cSGarrett D'Amore  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
167a4f122cSGarrett D'Amore  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
177a4f122cSGarrett D'Amore  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
187a4f122cSGarrett D'Amore  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
197a4f122cSGarrett D'Amore  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
207a4f122cSGarrett D'Amore  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
217a4f122cSGarrett D'Amore  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
227a4f122cSGarrett D'Amore  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
237a4f122cSGarrett D'Amore  * SUCH DAMAGE.
247a4f122cSGarrett D'Amore  */
257a4f122cSGarrett D'Amore 
267a4f122cSGarrett D'Amore /*
27*68c47f65SGarrett D'Amore  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
287a4f122cSGarrett D'Amore  * Use is subject to license terms.
297a4f122cSGarrett D'Amore  */
307a4f122cSGarrett D'Amore 
317a4f122cSGarrett D'Amore /*
327a4f122cSGarrett D'Amore  * Copyright (C) 4Front Technologies 1996-2008.
337a4f122cSGarrett D'Amore  */
347a4f122cSGarrett D'Amore 
357a4f122cSGarrett D'Amore #include <sys/audio/audio_driver.h>
367a4f122cSGarrett D'Amore #include <sys/note.h>
377a4f122cSGarrett D'Amore #include <sys/pci.h>
387a4f122cSGarrett D'Amore #include <sys/stdbool.h>
397a4f122cSGarrett D'Amore 
407a4f122cSGarrett D'Amore 
417a4f122cSGarrett D'Amore /*
427a4f122cSGarrett D'Amore  * NB: The Solo-1 is a bit schizophrenic compared to most devices.
437a4f122cSGarrett D'Amore  * It has two separate DMA engines for PCM data.  The first can do
447a4f122cSGarrett D'Amore  * either capture or playback, and supports various Sound Blaster
457a4f122cSGarrett D'Amore  * compatibility features.  The second is dedicated to playback.  The
467a4f122cSGarrett D'Amore  * two engines have very little in common when it comes to programming
477a4f122cSGarrett D'Amore  * them.
487a4f122cSGarrett D'Amore  *
497a4f122cSGarrett D'Amore  * We configure engine 1 for record, and engine 2 for playback.  Both
507a4f122cSGarrett D'Amore  * are configured for 48 kHz stereo 16-bit signed PCM.
517a4f122cSGarrett D'Amore  */
527a4f122cSGarrett D'Amore 
537a4f122cSGarrett D'Amore /*
547a4f122cSGarrett D'Amore  * ESS Solo-1 only implements the low 24-bits on Audio1, and requires
557a4f122cSGarrett D'Amore  * 64KB alignment.  For Audio2, it implements the full 32-bit address
567a4f122cSGarrett D'Amore  * space, but requires a 1MB address boundary.  Audio1 is used for
577a4f122cSGarrett D'Amore  * recording, and Audio2 is used for playback.
587a4f122cSGarrett D'Amore  */
597a4f122cSGarrett D'Amore static struct ddi_dma_attr dma_attr_audio1 = {
607a4f122cSGarrett D'Amore 	DMA_ATTR_VERSION,	/* dma_attr_version */
617a4f122cSGarrett D'Amore 	0x0,			/* dma_attr_addr_lo */
627a4f122cSGarrett D'Amore 	0x00ffffffU,		/* dma_attr_addr_hi */
637a4f122cSGarrett D'Amore 	0xffff,			/* dma_attr_count_max */
647a4f122cSGarrett D'Amore 	0x10000,		/* dma_attr_align */
657a4f122cSGarrett D'Amore 	0x7f,			/* dma_attr_burstsizes */
667a4f122cSGarrett D'Amore 	0x4,			/* dma_attr_minxfer */
677a4f122cSGarrett D'Amore 	0xffff,			/* dma_attr_maxxfer */
687a4f122cSGarrett D'Amore 	0xffff,			/* dma_attr_seg */
697a4f122cSGarrett D'Amore 	0x1,			/* dma_attr_sgllen */
707a4f122cSGarrett D'Amore 	0x1,			/* dma_attr_granular */
717a4f122cSGarrett D'Amore 	0			/* dma_attr_flags */
727a4f122cSGarrett D'Amore };
737a4f122cSGarrett D'Amore 
747a4f122cSGarrett D'Amore static struct ddi_dma_attr dma_attr_audio2 = {
757a4f122cSGarrett D'Amore 	DMA_ATTR_VERSION,	/* dma_attr_version */
767a4f122cSGarrett D'Amore 	0x0,			/* dma_attr_addr_lo */
777a4f122cSGarrett D'Amore 	0xffffffffU,		/* dma_attr_addr_hi */
787a4f122cSGarrett D'Amore 	0xfff0,			/* dma_attr_count_max */
797a4f122cSGarrett D'Amore 	0x100000,		/* dma_attr_align */
807a4f122cSGarrett D'Amore 	0x7f,			/* dma_attr_burstsizes */
817a4f122cSGarrett D'Amore 	0x4,			/* dma_attr_minxfer */
827a4f122cSGarrett D'Amore 	0xfff0,			/* dma_attr_maxxfer */
837a4f122cSGarrett D'Amore 	0xffff,			/* dma_attr_seg */
847a4f122cSGarrett D'Amore 	0x1,			/* dma_attr_sgllen */
857a4f122cSGarrett D'Amore 	0x1,			/* dma_attr_granular */
867a4f122cSGarrett D'Amore 	0			/* dma_attr_flags */
877a4f122cSGarrett D'Amore };
887a4f122cSGarrett D'Amore 
897a4f122cSGarrett D'Amore static ddi_device_acc_attr_t acc_attr = {
907a4f122cSGarrett D'Amore 	DDI_DEVICE_ATTR_V0,
917a4f122cSGarrett D'Amore 	DDI_STRUCTURE_LE_ACC,
927a4f122cSGarrett D'Amore 	DDI_STRICTORDER_ACC
937a4f122cSGarrett D'Amore };
947a4f122cSGarrett D'Amore 
957a4f122cSGarrett D'Amore static ddi_device_acc_attr_t buf_attr = {
967a4f122cSGarrett D'Amore 	DDI_DEVICE_ATTR_V0,
977a4f122cSGarrett D'Amore 	DDI_NEVERSWAP_ACC,
987a4f122cSGarrett D'Amore 	DDI_STRICTORDER_ACC
997a4f122cSGarrett D'Amore };
1007a4f122cSGarrett D'Amore 
1017a4f122cSGarrett D'Amore 
1027a4f122cSGarrett D'Amore /*
1037a4f122cSGarrett D'Amore  * For the sake of simplicity, this driver fixes a few parameters with
104*68c47f65SGarrett D'Amore  * constants.
1057a4f122cSGarrett D'Amore  */
1067a4f122cSGarrett D'Amore #define	SOLO_RATE	48000
107*68c47f65SGarrett D'Amore #define	SOLO_FRAGFR	1024
108*68c47f65SGarrett D'Amore #define	SOLO_NFRAGS	2
1097a4f122cSGarrett D'Amore #define	SOLO_NCHAN	2
1107a4f122cSGarrett D'Amore #define	SOLO_SAMPSZ	2
1117a4f122cSGarrett D'Amore #define	SOLO_FRAGSZ	(SOLO_FRAGFR * (SOLO_NCHAN * SOLO_SAMPSZ))
1127a4f122cSGarrett D'Amore #define	SOLO_BUFFR	(SOLO_NFRAGS * SOLO_FRAGFR)
1137a4f122cSGarrett D'Amore #define	SOLO_BUFSZ	(SOLO_NFRAGS * SOLO_FRAGSZ)
1147a4f122cSGarrett D'Amore 
1157a4f122cSGarrett D'Amore #define	INPUT_MIC	0
1167a4f122cSGarrett D'Amore #define	INPUT_LINE	1
1177a4f122cSGarrett D'Amore #define	INPUT_CD	2
1187a4f122cSGarrett D'Amore #define	INPUT_AUX	3
1197a4f122cSGarrett D'Amore #define	INPUT_MONO	4
1207a4f122cSGarrett D'Amore #define	INSRCS		0x1f		/* bits 0-4 */
1217a4f122cSGarrett D'Amore 
1227a4f122cSGarrett D'Amore #define	DRVNAME		"audiosolo"
1237a4f122cSGarrett D'Amore 
1247a4f122cSGarrett D'Amore static const char *solo_insrcs[] = {
1257a4f122cSGarrett D'Amore 	AUDIO_PORT_MIC,
1267a4f122cSGarrett D'Amore 	AUDIO_PORT_LINEIN,
1277a4f122cSGarrett D'Amore 	AUDIO_PORT_CD,
1287a4f122cSGarrett D'Amore 	AUDIO_PORT_AUX1IN,
1297a4f122cSGarrett D'Amore 	AUDIO_PORT_AUX2IN,	/* this is really mono-in */
1307a4f122cSGarrett D'Amore 	NULL
1317a4f122cSGarrett D'Amore };
1327a4f122cSGarrett D'Amore 
1337a4f122cSGarrett D'Amore typedef struct solo_regs {
1347a4f122cSGarrett D'Amore 	ddi_acc_handle_t	acch;
1357a4f122cSGarrett D'Amore 	caddr_t			base;
1367a4f122cSGarrett D'Amore } solo_regs_t;
1377a4f122cSGarrett D'Amore 
1387a4f122cSGarrett D'Amore typedef struct solo_engine {
1397a4f122cSGarrett D'Amore 	struct solo_dev		*dev;
1407a4f122cSGarrett D'Amore 	audio_engine_t		*engine;
1417a4f122cSGarrett D'Amore 	ddi_dma_handle_t	dmah;
1427a4f122cSGarrett D'Amore 	ddi_acc_handle_t	acch;
1437a4f122cSGarrett D'Amore 	caddr_t			kaddr;
1447a4f122cSGarrett D'Amore 	uint32_t		paddr;
1457a4f122cSGarrett D'Amore 
1467a4f122cSGarrett D'Amore 	bool			started;
147*68c47f65SGarrett D'Amore 	bool			trigger;
1487a4f122cSGarrett D'Amore 	uint64_t		count;
1497a4f122cSGarrett D'Amore 	uint16_t		offset;
1507a4f122cSGarrett D'Amore 	int			syncdir;
1517a4f122cSGarrett D'Amore 	int			format;
1527a4f122cSGarrett D'Amore 	bool			swapped;
1537a4f122cSGarrett D'Amore 
1547a4f122cSGarrett D'Amore 	void			(*start)(struct solo_engine *);
1557a4f122cSGarrett D'Amore 	void			(*stop)(struct solo_engine *);
1567a4f122cSGarrett D'Amore 	void			(*update)(struct solo_engine *);
1577a4f122cSGarrett D'Amore } solo_engine_t;
1587a4f122cSGarrett D'Amore 
1597a4f122cSGarrett D'Amore typedef enum {
1607a4f122cSGarrett D'Amore 	CTL_FRONT = 0,
1617a4f122cSGarrett D'Amore 	CTL_VOLUME,
1627a4f122cSGarrett D'Amore 	CTL_MIC,
1637a4f122cSGarrett D'Amore 	CTL_LINE,
1647a4f122cSGarrett D'Amore 	CTL_CD,
1657a4f122cSGarrett D'Amore 	CTL_AUX,
1667a4f122cSGarrett D'Amore 	CTL_MONO,
1677a4f122cSGarrett D'Amore 	CTL_MICBOOST,
1687a4f122cSGarrett D'Amore 	CTL_RECGAIN,
1697a4f122cSGarrett D'Amore 	CTL_RECSRC,
1707a4f122cSGarrett D'Amore 	CTL_MONSRC,
1717a4f122cSGarrett D'Amore 	CTL_SPEAKER,
1727a4f122cSGarrett D'Amore 	CTL_LOOPBACK,
1737a4f122cSGarrett D'Amore 	CTL_NUM,			/* must be last */
1747a4f122cSGarrett D'Amore } solo_ctrl_num_t;
1757a4f122cSGarrett D'Amore 
1767a4f122cSGarrett D'Amore typedef struct solo_ctrl {
1777a4f122cSGarrett D'Amore 	struct solo_dev		*dev;
1787a4f122cSGarrett D'Amore 	audio_ctrl_t		*ctrl;
1797a4f122cSGarrett D'Amore 	solo_ctrl_num_t		num;
1807a4f122cSGarrett D'Amore 	uint64_t		val;
1817a4f122cSGarrett D'Amore } solo_ctrl_t;
1827a4f122cSGarrett D'Amore 
1837a4f122cSGarrett D'Amore typedef struct solo_dev {
1847a4f122cSGarrett D'Amore 	dev_info_t		*dip;
1857a4f122cSGarrett D'Amore 	audio_dev_t		*adev;
1867a4f122cSGarrett D'Amore 	kmutex_t		mutex;
1877a4f122cSGarrett D'Amore 	ddi_intr_handle_t	ihandle;
1887a4f122cSGarrett D'Amore 
1897a4f122cSGarrett D'Amore 	bool			suspended;
1907a4f122cSGarrett D'Amore 
1917a4f122cSGarrett D'Amore 	/*
1927a4f122cSGarrett D'Amore 	 * Audio engines
1937a4f122cSGarrett D'Amore 	 */
1947a4f122cSGarrett D'Amore 	solo_engine_t		rec;
1957a4f122cSGarrett D'Amore 	solo_engine_t		play;
1967a4f122cSGarrett D'Amore 	uint32_t		last_capture;
1977a4f122cSGarrett D'Amore 
1987a4f122cSGarrett D'Amore 	/*
1997a4f122cSGarrett D'Amore 	 * Controls.
2007a4f122cSGarrett D'Amore 	 */
2017a4f122cSGarrett D'Amore 	solo_ctrl_t		ctrls[CTL_NUM];
2027a4f122cSGarrett D'Amore 
2037a4f122cSGarrett D'Amore 	/*
2047a4f122cSGarrett D'Amore 	 * Mapped registers
2057a4f122cSGarrett D'Amore 	 */
2067a4f122cSGarrett D'Amore 	ddi_acc_handle_t	pcih;
2077a4f122cSGarrett D'Amore 	solo_regs_t		io;
2087a4f122cSGarrett D'Amore 	solo_regs_t		sb;
2097a4f122cSGarrett D'Amore 	solo_regs_t		vc;
2107a4f122cSGarrett D'Amore 
2117a4f122cSGarrett D'Amore } solo_dev_t;
2127a4f122cSGarrett D'Amore 
2137a4f122cSGarrett D'Amore /*
2147a4f122cSGarrett D'Amore  * Common code for the pcm function
2157a4f122cSGarrett D'Amore  *
2167a4f122cSGarrett D'Amore  * solo_cmd write a single byte to the CMD port.
2177a4f122cSGarrett D'Amore  * solo_cmd1 write a CMD + 1 byte arg
2187a4f122cSGarrett D'Amore  * ess_get_byte returns a single byte from the DSP data port
2197a4f122cSGarrett D'Amore  *
2207a4f122cSGarrett D'Amore  * solo_write is actually solo_cmd1
2217a4f122cSGarrett D'Amore  * solo_read access ext. regs via solo_cmd(0xc0, reg) followed by solo_get_byte
2227a4f122cSGarrett D'Amore  */
2237a4f122cSGarrett D'Amore 
2247a4f122cSGarrett D'Amore #define	PORT_RD8(port, regno)		\
2257a4f122cSGarrett D'Amore 	ddi_get8(port.acch, (void *)(port.base + (regno)))
2267a4f122cSGarrett D'Amore #define	PORT_RD16(port, regno)		\
2277a4f122cSGarrett D'Amore 	ddi_get16(port.acch, (void *)(port.base + (regno)))
2287a4f122cSGarrett D'Amore #define	PORT_RD32(port, regno)		\
2297a4f122cSGarrett D'Amore 	ddi_get32(port.acch, (void *)(port.base + (regno)))
2307a4f122cSGarrett D'Amore #define	PORT_WR8(port, regno, data)	\
2317a4f122cSGarrett D'Amore 	ddi_put8(port.acch, (void *)(port.base + (regno)), data)
2327a4f122cSGarrett D'Amore #define	PORT_WR16(port, regno, data)	\
2337a4f122cSGarrett D'Amore 	ddi_put16(port.acch, (void *)(port.base + (regno)), data)
2347a4f122cSGarrett D'Amore #define	PORT_WR32(port, regno, data)	\
2357a4f122cSGarrett D'Amore 	ddi_put32(port.acch, (void *)(port.base + (regno)), data)
2367a4f122cSGarrett D'Amore 
2377a4f122cSGarrett D'Amore static bool
solo_dspready(solo_dev_t * dev)2387a4f122cSGarrett D'Amore solo_dspready(solo_dev_t *dev)
2397a4f122cSGarrett D'Amore {
2407a4f122cSGarrett D'Amore 	return ((PORT_RD8(dev->sb, 0xc) & 0x80) == 0 ? true : false);
2417a4f122cSGarrett D'Amore }
2427a4f122cSGarrett D'Amore 
2437a4f122cSGarrett D'Amore static bool
solo_dspwr(solo_dev_t * dev,uint8_t val)2447a4f122cSGarrett D'Amore solo_dspwr(solo_dev_t *dev, uint8_t val)
2457a4f122cSGarrett D'Amore {
2467a4f122cSGarrett D'Amore 	int  i;
2477a4f122cSGarrett D'Amore 
2487a4f122cSGarrett D'Amore 	for (i = 0; i < 1000; i++) {
2497a4f122cSGarrett D'Amore 		if (solo_dspready(dev)) {
2507a4f122cSGarrett D'Amore 			PORT_WR8(dev->sb, 0xc, val);
2517a4f122cSGarrett D'Amore 			return (true);
2527a4f122cSGarrett D'Amore 		}
2537a4f122cSGarrett D'Amore 		if (i > 10)
2547a4f122cSGarrett D'Amore 			drv_usecwait((i > 100)? 1000 : 10);
2557a4f122cSGarrett D'Amore 	}
2567a4f122cSGarrett D'Amore 	audio_dev_warn(dev->adev, "solo_dspwr(0x%02x) timed out", val);
2577a4f122cSGarrett D'Amore 	return (false);
2587a4f122cSGarrett D'Amore }
2597a4f122cSGarrett D'Amore 
2607a4f122cSGarrett D'Amore static bool
solo_cmd(solo_dev_t * dev,uint8_t val)2617a4f122cSGarrett D'Amore solo_cmd(solo_dev_t *dev, uint8_t val)
2627a4f122cSGarrett D'Amore {
2637a4f122cSGarrett D'Amore 	return (solo_dspwr(dev, val));
2647a4f122cSGarrett D'Amore }
2657a4f122cSGarrett D'Amore 
2667a4f122cSGarrett D'Amore static void
solo_cmd1(solo_dev_t * dev,uint8_t cmd,uint8_t val)2677a4f122cSGarrett D'Amore solo_cmd1(solo_dev_t *dev, uint8_t cmd, uint8_t val)
2687a4f122cSGarrett D'Amore {
2697a4f122cSGarrett D'Amore 	if (solo_dspwr(dev, cmd)) {
2707a4f122cSGarrett D'Amore 		(void) solo_dspwr(dev, val);
2717a4f122cSGarrett D'Amore 	}
2727a4f122cSGarrett D'Amore }
2737a4f122cSGarrett D'Amore 
2747a4f122cSGarrett D'Amore static void
solo_setmixer(solo_dev_t * dev,uint8_t port,uint8_t value)2757a4f122cSGarrett D'Amore solo_setmixer(solo_dev_t *dev, uint8_t port, uint8_t value)
2767a4f122cSGarrett D'Amore {
2777a4f122cSGarrett D'Amore 	PORT_WR8(dev->sb, 0x4, port); /* Select register */
2787a4f122cSGarrett D'Amore 	drv_usecwait(10);
2797a4f122cSGarrett D'Amore 	PORT_WR8(dev->sb, 0x5, value);
2807a4f122cSGarrett D'Amore 	drv_usecwait(10);
2817a4f122cSGarrett D'Amore }
2827a4f122cSGarrett D'Amore 
2837a4f122cSGarrett D'Amore static uint8_t
solo_getmixer(solo_dev_t * dev,uint8_t port)2847a4f122cSGarrett D'Amore solo_getmixer(solo_dev_t *dev, uint8_t port)
2857a4f122cSGarrett D'Amore {
2867a4f122cSGarrett D'Amore 	uint8_t val;
2877a4f122cSGarrett D'Amore 
2887a4f122cSGarrett D'Amore 	PORT_WR8(dev->sb, 0x4, port); /* Select register */
2897a4f122cSGarrett D'Amore 	drv_usecwait(10);
2907a4f122cSGarrett D'Amore 	val = PORT_RD8(dev->sb, 0x5);
2917a4f122cSGarrett D'Amore 	drv_usecwait(10);
2927a4f122cSGarrett D'Amore 
2937a4f122cSGarrett D'Amore 	return (val);
2947a4f122cSGarrett D'Amore }
2957a4f122cSGarrett D'Amore 
2967a4f122cSGarrett D'Amore static uint8_t
solo_get_byte(solo_dev_t * dev)2977a4f122cSGarrett D'Amore solo_get_byte(solo_dev_t *dev)
2987a4f122cSGarrett D'Amore {
2997a4f122cSGarrett D'Amore 	for (int i = 1000; i > 0; i--) {
3007a4f122cSGarrett D'Amore 		if (PORT_RD8(dev->sb, 0xc) & 0x40)
3017a4f122cSGarrett D'Amore 			return (PORT_RD8(dev->sb, 0xa));
3027a4f122cSGarrett D'Amore 		else
3037a4f122cSGarrett D'Amore 			drv_usecwait(20);
3047a4f122cSGarrett D'Amore 	}
3057a4f122cSGarrett D'Amore 	audio_dev_warn(dev->adev, "timeout waiting to read DSP port");
3067a4f122cSGarrett D'Amore 	return (0xff);
3077a4f122cSGarrett D'Amore }
3087a4f122cSGarrett D'Amore 
3097a4f122cSGarrett D'Amore static void
solo_write(solo_dev_t * dev,uint8_t reg,uint8_t val)3107a4f122cSGarrett D'Amore solo_write(solo_dev_t *dev, uint8_t reg, uint8_t val)
3117a4f122cSGarrett D'Amore {
3127a4f122cSGarrett D'Amore 	solo_cmd1(dev, reg, val);
3137a4f122cSGarrett D'Amore }
3147a4f122cSGarrett D'Amore 
3157a4f122cSGarrett D'Amore static uint8_t
solo_read(solo_dev_t * dev,uint8_t reg)3167a4f122cSGarrett D'Amore solo_read(solo_dev_t *dev, uint8_t reg)
3177a4f122cSGarrett D'Amore {
3187a4f122cSGarrett D'Amore 	if (solo_cmd(dev, 0xc0) && solo_cmd(dev, reg)) {
3197a4f122cSGarrett D'Amore 		return (solo_get_byte(dev));
3207a4f122cSGarrett D'Amore 	}
3217a4f122cSGarrett D'Amore 	return (0xff);
3227a4f122cSGarrett D'Amore }
3237a4f122cSGarrett D'Amore 
3247a4f122cSGarrett D'Amore static bool
solo_reset_dsp(solo_dev_t * dev)3257a4f122cSGarrett D'Amore solo_reset_dsp(solo_dev_t *dev)
3267a4f122cSGarrett D'Amore {
3277a4f122cSGarrett D'Amore 	PORT_WR8(dev->sb, 0x6, 3);
3287a4f122cSGarrett D'Amore 	drv_usecwait(100);
3297a4f122cSGarrett D'Amore 	PORT_WR8(dev->sb, 0x6, 0);
3307a4f122cSGarrett D'Amore 	if (solo_get_byte(dev) != 0xAA) {
3317a4f122cSGarrett D'Amore 		audio_dev_warn(dev->adev, "solo_reset_dsp failed");
3327a4f122cSGarrett D'Amore 		return (false);	/* Sorry */
3337a4f122cSGarrett D'Amore 	}
3347a4f122cSGarrett D'Amore 	return (true);
3357a4f122cSGarrett D'Amore }
3367a4f122cSGarrett D'Amore 
3377a4f122cSGarrett D'Amore static uint_t
solo_intr(caddr_t arg1,caddr_t arg2)3387a4f122cSGarrett D'Amore solo_intr(caddr_t arg1, caddr_t arg2)
3397a4f122cSGarrett D'Amore {
3407a4f122cSGarrett D'Amore 	solo_dev_t	*dev = (void *)arg1;
3417a4f122cSGarrett D'Amore 	uint8_t		status;
3427a4f122cSGarrett D'Amore 	uint_t		rv = DDI_INTR_UNCLAIMED;
3437a4f122cSGarrett D'Amore 
3447a4f122cSGarrett D'Amore 	_NOTE(ARGUNUSED(arg2));
3457a4f122cSGarrett D'Amore 
3467a4f122cSGarrett D'Amore 	mutex_enter(&dev->mutex);
3477a4f122cSGarrett D'Amore 
3487a4f122cSGarrett D'Amore 	if (dev->suspended) {
3497a4f122cSGarrett D'Amore 		mutex_exit(&dev->mutex);
3507a4f122cSGarrett D'Amore 		return (rv);
3517a4f122cSGarrett D'Amore 	}
3527a4f122cSGarrett D'Amore 
3537a4f122cSGarrett D'Amore 	status = PORT_RD8(dev->io, 0x7);
3547a4f122cSGarrett D'Amore 	if (status & 0x20) {
3557a4f122cSGarrett D'Amore 		rv = DDI_INTR_CLAIMED;
3567a4f122cSGarrett D'Amore 		/* ack the interrupt */
3577a4f122cSGarrett D'Amore 		solo_setmixer(dev, 0x7a, solo_getmixer(dev, 0x7a) & ~0x80);
3587a4f122cSGarrett D'Amore 	}
3597a4f122cSGarrett D'Amore 
3607a4f122cSGarrett D'Amore 	if (status & 0x10) {
3617a4f122cSGarrett D'Amore 		rv = DDI_INTR_CLAIMED;
3627a4f122cSGarrett D'Amore 		/* ack the interrupt */
3637a4f122cSGarrett D'Amore 		(void) PORT_RD8(dev->sb, 0xe);
3647a4f122cSGarrett D'Amore 	}
3657a4f122cSGarrett D'Amore 	mutex_exit(&dev->mutex);
3667a4f122cSGarrett D'Amore 
3677a4f122cSGarrett D'Amore 	return (rv);
3687a4f122cSGarrett D'Amore }
3697a4f122cSGarrett D'Amore 
3707a4f122cSGarrett D'Amore static uint8_t
solo_mixer_scale(solo_dev_t * dev,solo_ctrl_num_t num)3717a4f122cSGarrett D'Amore solo_mixer_scale(solo_dev_t *dev, solo_ctrl_num_t num)
3727a4f122cSGarrett D'Amore {
3737a4f122cSGarrett D'Amore 	uint32_t	l, r;
3747a4f122cSGarrett D'Amore 	uint64_t	value = dev->ctrls[num].val;
3757a4f122cSGarrett D'Amore 
3767a4f122cSGarrett D'Amore 	l = (value >> 8) & 0xff;
3777a4f122cSGarrett D'Amore 	r = value & 0xff;
3787a4f122cSGarrett D'Amore 
3797a4f122cSGarrett D'Amore 	l = (l * 15) / 100;
3807a4f122cSGarrett D'Amore 	r = (r * 15) / 100;
3817a4f122cSGarrett D'Amore 	return ((uint8_t)((l << 4) | (r)));
3827a4f122cSGarrett D'Amore }
3837a4f122cSGarrett D'Amore 
3847a4f122cSGarrett D'Amore static void
solo_configure_mixer(solo_dev_t * dev)3857a4f122cSGarrett D'Amore solo_configure_mixer(solo_dev_t *dev)
3867a4f122cSGarrett D'Amore {
3877a4f122cSGarrett D'Amore 	uint32_t v;
3887a4f122cSGarrett D'Amore 	uint32_t mon, rec;
3897a4f122cSGarrett D'Amore 
3907a4f122cSGarrett D'Amore 	/*
3917a4f122cSGarrett D'Amore 	 * We disable hardware volume control (i.e. async updates to volume).
3927a4f122cSGarrett D'Amore 	 * We could in theory support this, but making it work right can be
3937a4f122cSGarrett D'Amore 	 * tricky, and we doubt it is widely used.
3947a4f122cSGarrett D'Amore 	 */
3957a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x64, solo_getmixer(dev, 0x64) | 0xc);
3967a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x66, 0);
3977a4f122cSGarrett D'Amore 
3987a4f122cSGarrett D'Amore 	/* master volume has 6 bits per channel, bit 6 indicates mute  */
3997a4f122cSGarrett D'Amore 	/* left */
4007a4f122cSGarrett D'Amore 	v = (dev->ctrls[CTL_FRONT].val >> 8) & 0xff;
4017a4f122cSGarrett D'Amore 	v = v ? (v * 63) / 100 : 64;
4027a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x60, v & 0xff);
4037a4f122cSGarrett D'Amore 
4047a4f122cSGarrett D'Amore 	/* right */
4057a4f122cSGarrett D'Amore 	v = dev->ctrls[CTL_FRONT].val & 0xff;
4067a4f122cSGarrett D'Amore 	v = v ? (v * 63) / 100 : 64;
4077a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x62, v & 0xff);
4087a4f122cSGarrett D'Amore 
4097a4f122cSGarrett D'Amore 	v = solo_mixer_scale(dev, CTL_VOLUME);
4107a4f122cSGarrett D'Amore 	v = v | (v << 4);
4117a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x7c, v & 0xff);
4127a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x14, v & 0xff);
4137a4f122cSGarrett D'Amore 
4147a4f122cSGarrett D'Amore 	mon = dev->ctrls[CTL_MONSRC].val;
4157a4f122cSGarrett D'Amore 	rec = dev->ctrls[CTL_RECSRC].val;
4167a4f122cSGarrett D'Amore 
4177a4f122cSGarrett D'Amore 	/*
4187a4f122cSGarrett D'Amore 	 * The Solo-1 has dual stereo mixers (one for input and one for output),
4197a4f122cSGarrett D'Amore 	 * with separate volume controls for each.
4207a4f122cSGarrett D'Amore 	 */
4217a4f122cSGarrett D'Amore 	v = solo_mixer_scale(dev, CTL_MIC);
4227a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x68, rec & (1 << INPUT_MIC) ? v : 0);
4237a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x1a, mon & (1 << INPUT_MIC) ? v : 0);
4247a4f122cSGarrett D'Amore 
4257a4f122cSGarrett D'Amore 	v = solo_mixer_scale(dev, CTL_LINE);
4267a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x6e, rec & (1 << INPUT_LINE) ? v : 0);
4277a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x3e, mon & (1 << INPUT_LINE) ? v : 0);
4287a4f122cSGarrett D'Amore 
4297a4f122cSGarrett D'Amore 	v = solo_mixer_scale(dev, CTL_CD);
4307a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x6a, rec & (1 << INPUT_CD) ? v : 0);
4317a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x38, mon & (1 << INPUT_CD) ? v : 0);
4327a4f122cSGarrett D'Amore 
4337a4f122cSGarrett D'Amore 	v = solo_mixer_scale(dev, CTL_AUX);
4347a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x6c, rec & (1 << INPUT_AUX) ? v : 0);
4357a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x3a, mon & (1 << INPUT_AUX) ? v : 0);
4367a4f122cSGarrett D'Amore 
4377a4f122cSGarrett D'Amore 	v = solo_mixer_scale(dev, CTL_MONO);
4387a4f122cSGarrett D'Amore 	v = v | (v << 4);
4397a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x6f, rec & (1 << INPUT_MONO) ? v : 0);
4407a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x6d, mon & (1 << INPUT_MONO) ? v : 0);
4417a4f122cSGarrett D'Amore 
4427a4f122cSGarrett D'Amore 	if (dev->ctrls[CTL_MICBOOST].val) {
4437a4f122cSGarrett D'Amore 		solo_setmixer(dev, 0x7d, solo_getmixer(dev, 0x7d) | 0x8);
4447a4f122cSGarrett D'Amore 	} else {
4457a4f122cSGarrett D'Amore 		solo_setmixer(dev, 0x7d, solo_getmixer(dev, 0x7d) & ~(0x8));
4467a4f122cSGarrett D'Amore 	}
4477a4f122cSGarrett D'Amore 
4487a4f122cSGarrett D'Amore 	v = solo_mixer_scale(dev, CTL_RECGAIN);
4497a4f122cSGarrett D'Amore 	v = v | (v << 4);
4507a4f122cSGarrett D'Amore 	solo_write(dev, 0xb4, v & 0xff);
4517a4f122cSGarrett D'Amore 
4527a4f122cSGarrett D'Amore 	v = dev->ctrls[CTL_SPEAKER].val & 0xff;
4537a4f122cSGarrett D'Amore 	v = (v * 7) / 100;
4547a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x3c, v & 0xff);
4557a4f122cSGarrett D'Amore 
4567a4f122cSGarrett D'Amore 	if (dev->ctrls[CTL_LOOPBACK].val) {
4577a4f122cSGarrett D'Amore 		/* record-what-you-hear mode */
4587a4f122cSGarrett D'Amore 		solo_setmixer(dev, 0x1c, 0x3);
4597a4f122cSGarrett D'Amore 	} else {
4607a4f122cSGarrett D'Amore 		/* use record mixer */
4617a4f122cSGarrett D'Amore 		solo_setmixer(dev, 0x1c, 0x5);
4627a4f122cSGarrett D'Amore 	}
4637a4f122cSGarrett D'Amore 
4647a4f122cSGarrett D'Amore }
4657a4f122cSGarrett D'Amore 
4667a4f122cSGarrett D'Amore static int
solo_set_mixsrc(void * arg,uint64_t val)4677a4f122cSGarrett D'Amore solo_set_mixsrc(void *arg, uint64_t val)
4687a4f122cSGarrett D'Amore {
4697a4f122cSGarrett D'Amore 	solo_ctrl_t	*pc = arg;
4707a4f122cSGarrett D'Amore 	solo_dev_t	*dev = pc->dev;
4717a4f122cSGarrett D'Amore 
4727a4f122cSGarrett D'Amore 	if ((val & ~INSRCS) != 0)
4737a4f122cSGarrett D'Amore 		return (EINVAL);
4747a4f122cSGarrett D'Amore 
4757a4f122cSGarrett D'Amore 	mutex_enter(&dev->mutex);
4767a4f122cSGarrett D'Amore 	pc->val = val;
477*68c47f65SGarrett D'Amore 	solo_configure_mixer(dev);
4787a4f122cSGarrett D'Amore 	mutex_exit(&dev->mutex);
4797a4f122cSGarrett D'Amore 	return (0);
4807a4f122cSGarrett D'Amore }
4817a4f122cSGarrett D'Amore 
4827a4f122cSGarrett D'Amore static int
solo_set_mono(void * arg,uint64_t val)4837a4f122cSGarrett D'Amore solo_set_mono(void *arg, uint64_t val)
4847a4f122cSGarrett D'Amore {
4857a4f122cSGarrett D'Amore 	solo_ctrl_t	*pc = arg;
4867a4f122cSGarrett D'Amore 	solo_dev_t	*dev = pc->dev;
4877a4f122cSGarrett D'Amore 
4887a4f122cSGarrett D'Amore 	val &= 0xff;
4897a4f122cSGarrett D'Amore 	if (val > 100)
4907a4f122cSGarrett D'Amore 		return (EINVAL);
4917a4f122cSGarrett D'Amore 
4927a4f122cSGarrett D'Amore 	val = (val & 0xff) | ((val & 0xff) << 8);
4937a4f122cSGarrett D'Amore 
4947a4f122cSGarrett D'Amore 	mutex_enter(&dev->mutex);
4957a4f122cSGarrett D'Amore 	pc->val = val;
496*68c47f65SGarrett D'Amore 	solo_configure_mixer(dev);
4977a4f122cSGarrett D'Amore 	mutex_exit(&dev->mutex);
4987a4f122cSGarrett D'Amore 	return (0);
4997a4f122cSGarrett D'Amore }
5007a4f122cSGarrett D'Amore 
5017a4f122cSGarrett D'Amore static int
solo_set_stereo(void * arg,uint64_t val)5027a4f122cSGarrett D'Amore solo_set_stereo(void *arg, uint64_t val)
5037a4f122cSGarrett D'Amore {
5047a4f122cSGarrett D'Amore 	solo_ctrl_t	*pc = arg;
5057a4f122cSGarrett D'Amore 	solo_dev_t	*dev = pc->dev;
5067a4f122cSGarrett D'Amore 	uint8_t		l;
5077a4f122cSGarrett D'Amore 	uint8_t		r;
5087a4f122cSGarrett D'Amore 
5097a4f122cSGarrett D'Amore 	l = (val & 0xff00) >> 8;
5107a4f122cSGarrett D'Amore 	r = val & 0xff;
5117a4f122cSGarrett D'Amore 
5127a4f122cSGarrett D'Amore 	if ((l > 100) || (r > 100))
5137a4f122cSGarrett D'Amore 		return (EINVAL);
5147a4f122cSGarrett D'Amore 
5157a4f122cSGarrett D'Amore 	mutex_enter(&dev->mutex);
5167a4f122cSGarrett D'Amore 	pc->val = val;
517*68c47f65SGarrett D'Amore 	solo_configure_mixer(dev);
5187a4f122cSGarrett D'Amore 	mutex_exit(&dev->mutex);
5197a4f122cSGarrett D'Amore 	return (0);
5207a4f122cSGarrett D'Amore }
5217a4f122cSGarrett D'Amore 
5227a4f122cSGarrett D'Amore static int
solo_set_bool(void * arg,uint64_t val)5237a4f122cSGarrett D'Amore solo_set_bool(void *arg, uint64_t val)
5247a4f122cSGarrett D'Amore {
5257a4f122cSGarrett D'Amore 	solo_ctrl_t	*pc = arg;
5267a4f122cSGarrett D'Amore 	solo_dev_t	*dev = pc->dev;
5277a4f122cSGarrett D'Amore 
5287a4f122cSGarrett D'Amore 	mutex_enter(&dev->mutex);
5297a4f122cSGarrett D'Amore 	pc->val = val;
530*68c47f65SGarrett D'Amore 	solo_configure_mixer(dev);
5317a4f122cSGarrett D'Amore 	mutex_exit(&dev->mutex);
5327a4f122cSGarrett D'Amore 	return (0);
5337a4f122cSGarrett D'Amore }
5347a4f122cSGarrett D'Amore 
5357a4f122cSGarrett D'Amore static int
solo_get_value(void * arg,uint64_t * val)5367a4f122cSGarrett D'Amore solo_get_value(void *arg, uint64_t *val)
5377a4f122cSGarrett D'Amore {
5387a4f122cSGarrett D'Amore 	solo_ctrl_t	*pc = arg;
5397a4f122cSGarrett D'Amore 	solo_dev_t	*dev = pc->dev;
5407a4f122cSGarrett D'Amore 
5417a4f122cSGarrett D'Amore 	mutex_enter(&dev->mutex);
5427a4f122cSGarrett D'Amore 	*val = pc->val;
5437a4f122cSGarrett D'Amore 	mutex_exit(&dev->mutex);
5447a4f122cSGarrett D'Amore 	return (0);
5457a4f122cSGarrett D'Amore }
5467a4f122cSGarrett D'Amore 
5477a4f122cSGarrett D'Amore #define	PLAYCTL	(AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_PLAY)
5487a4f122cSGarrett D'Amore #define	RECCTL	(AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_REC)
5497a4f122cSGarrett D'Amore #define	MONCTL	(AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_MONITOR)
5507a4f122cSGarrett D'Amore #define	PCMVOL	(PLAYCTL | AUDIO_CTRL_FLAG_PCMVOL)
5517a4f122cSGarrett D'Amore #define	MAINVOL	(PLAYCTL | AUDIO_CTRL_FLAG_MAINVOL)
5527a4f122cSGarrett D'Amore #define	RECVOL	(RECCTL | AUDIO_CTRL_FLAG_RECVOL)
5537a4f122cSGarrett D'Amore 
5547a4f122cSGarrett D'Amore static void
solo_alloc_ctrl(solo_dev_t * dev,uint32_t num,uint64_t val)5557a4f122cSGarrett D'Amore solo_alloc_ctrl(solo_dev_t *dev, uint32_t num, uint64_t val)
5567a4f122cSGarrett D'Amore {
5577a4f122cSGarrett D'Amore 	audio_ctrl_desc_t	desc;
5587a4f122cSGarrett D'Amore 	audio_ctrl_wr_t		fn;
5597a4f122cSGarrett D'Amore 	solo_ctrl_t		*pc;
5607a4f122cSGarrett D'Amore 
5617a4f122cSGarrett D'Amore 	bzero(&desc, sizeof (desc));
5627a4f122cSGarrett D'Amore 
5637a4f122cSGarrett D'Amore 	pc = &dev->ctrls[num];
5647a4f122cSGarrett D'Amore 	pc->num = num;
5657a4f122cSGarrett D'Amore 	pc->dev = dev;
5667a4f122cSGarrett D'Amore 
5677a4f122cSGarrett D'Amore 	switch (num) {
5687a4f122cSGarrett D'Amore 	case CTL_VOLUME:
5697a4f122cSGarrett D'Amore 		desc.acd_name = AUDIO_CTRL_ID_VOLUME;
5707a4f122cSGarrett D'Amore 		desc.acd_type = AUDIO_CTRL_TYPE_MONO;
5717a4f122cSGarrett D'Amore 		desc.acd_minvalue = 0;
5727a4f122cSGarrett D'Amore 		desc.acd_maxvalue = 100;
5737a4f122cSGarrett D'Amore 		desc.acd_flags = PCMVOL;
5747a4f122cSGarrett D'Amore 		fn = solo_set_mono;
5757a4f122cSGarrett D'Amore 		break;
5767a4f122cSGarrett D'Amore 
5777a4f122cSGarrett D'Amore 	case CTL_FRONT:
5787a4f122cSGarrett D'Amore 		desc.acd_name = AUDIO_CTRL_ID_LINEOUT;
5797a4f122cSGarrett D'Amore 		desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
5807a4f122cSGarrett D'Amore 		desc.acd_minvalue = 0;
5817a4f122cSGarrett D'Amore 		desc.acd_maxvalue = 100;
5827a4f122cSGarrett D'Amore 		desc.acd_flags = MAINVOL;
5837a4f122cSGarrett D'Amore 		fn = solo_set_stereo;
5847a4f122cSGarrett D'Amore 		break;
5857a4f122cSGarrett D'Amore 
5867a4f122cSGarrett D'Amore 	case CTL_SPEAKER:
5877a4f122cSGarrett D'Amore 		desc.acd_name = AUDIO_CTRL_ID_SPEAKER;
5887a4f122cSGarrett D'Amore 		desc.acd_type = AUDIO_CTRL_TYPE_MONO;
5897a4f122cSGarrett D'Amore 		desc.acd_minvalue = 0;
5907a4f122cSGarrett D'Amore 		desc.acd_maxvalue = 100;
5917a4f122cSGarrett D'Amore 		desc.acd_flags = MAINVOL;
5927a4f122cSGarrett D'Amore 		fn = solo_set_mono;
5937a4f122cSGarrett D'Amore 		break;
5947a4f122cSGarrett D'Amore 
5957a4f122cSGarrett D'Amore 	case CTL_MIC:
5967a4f122cSGarrett D'Amore 		desc.acd_name = AUDIO_CTRL_ID_MIC;
5977a4f122cSGarrett D'Amore 		desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
5987a4f122cSGarrett D'Amore 		desc.acd_minvalue = 0;
5997a4f122cSGarrett D'Amore 		desc.acd_maxvalue = 100;
6007a4f122cSGarrett D'Amore 		desc.acd_flags = RECVOL;
6017a4f122cSGarrett D'Amore 		fn = solo_set_stereo;
6027a4f122cSGarrett D'Amore 		break;
6037a4f122cSGarrett D'Amore 
6047a4f122cSGarrett D'Amore 	case CTL_LINE:
6057a4f122cSGarrett D'Amore 		desc.acd_name = AUDIO_CTRL_ID_LINEIN;
6067a4f122cSGarrett D'Amore 		desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
6077a4f122cSGarrett D'Amore 		desc.acd_minvalue = 0;
6087a4f122cSGarrett D'Amore 		desc.acd_maxvalue = 100;
6097a4f122cSGarrett D'Amore 		desc.acd_flags = RECVOL;
6107a4f122cSGarrett D'Amore 		fn = solo_set_stereo;
6117a4f122cSGarrett D'Amore 		break;
6127a4f122cSGarrett D'Amore 
6137a4f122cSGarrett D'Amore 	case CTL_CD:
6147a4f122cSGarrett D'Amore 		desc.acd_name = AUDIO_CTRL_ID_CD;
6157a4f122cSGarrett D'Amore 		desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
6167a4f122cSGarrett D'Amore 		desc.acd_minvalue = 0;
6177a4f122cSGarrett D'Amore 		desc.acd_maxvalue = 100;
6187a4f122cSGarrett D'Amore 		desc.acd_flags = RECVOL;
6197a4f122cSGarrett D'Amore 		fn = solo_set_stereo;
6207a4f122cSGarrett D'Amore 		break;
6217a4f122cSGarrett D'Amore 
6227a4f122cSGarrett D'Amore 	case CTL_AUX:
6237a4f122cSGarrett D'Amore 		desc.acd_name = AUDIO_CTRL_ID_AUX1IN;
6247a4f122cSGarrett D'Amore 		desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
6257a4f122cSGarrett D'Amore 		desc.acd_minvalue = 0;
6267a4f122cSGarrett D'Amore 		desc.acd_maxvalue = 100;
6277a4f122cSGarrett D'Amore 		desc.acd_flags = RECVOL;
6287a4f122cSGarrett D'Amore 		fn = solo_set_stereo;
6297a4f122cSGarrett D'Amore 		break;
6307a4f122cSGarrett D'Amore 
6317a4f122cSGarrett D'Amore 	case CTL_MONO:
6327a4f122cSGarrett D'Amore 		desc.acd_name = AUDIO_CTRL_ID_AUX2IN;
6337a4f122cSGarrett D'Amore 		desc.acd_type = AUDIO_CTRL_TYPE_MONO;
6347a4f122cSGarrett D'Amore 		desc.acd_minvalue = 0;
6357a4f122cSGarrett D'Amore 		desc.acd_maxvalue = 100;
6367a4f122cSGarrett D'Amore 		desc.acd_flags = RECVOL;
6377a4f122cSGarrett D'Amore 		fn = solo_set_mono;
6387a4f122cSGarrett D'Amore 		break;
6397a4f122cSGarrett D'Amore 
6407a4f122cSGarrett D'Amore 	case CTL_RECSRC:
6417a4f122cSGarrett D'Amore 		desc.acd_name = AUDIO_CTRL_ID_RECSRC;
6427a4f122cSGarrett D'Amore 		desc.acd_type = AUDIO_CTRL_TYPE_ENUM;
6437a4f122cSGarrett D'Amore 		desc.acd_minvalue = INSRCS;
6447a4f122cSGarrett D'Amore 		desc.acd_maxvalue = INSRCS;
6457a4f122cSGarrett D'Amore 		desc.acd_flags = RECCTL | AUDIO_CTRL_FLAG_MULTI;
6467a4f122cSGarrett D'Amore 		for (int i = 0; solo_insrcs[i]; i++) {
6477a4f122cSGarrett D'Amore 			desc.acd_enum[i] = solo_insrcs[i];
6487a4f122cSGarrett D'Amore 		}
6497a4f122cSGarrett D'Amore 		fn = solo_set_mixsrc;
6507a4f122cSGarrett D'Amore 		break;
6517a4f122cSGarrett D'Amore 
6527a4f122cSGarrett D'Amore 	case CTL_MONSRC:
6537a4f122cSGarrett D'Amore 		desc.acd_name = AUDIO_CTRL_ID_MONSRC;
6547a4f122cSGarrett D'Amore 		desc.acd_type = AUDIO_CTRL_TYPE_ENUM;
6557a4f122cSGarrett D'Amore 		desc.acd_minvalue = INSRCS;
6567a4f122cSGarrett D'Amore 		desc.acd_maxvalue = INSRCS;
6577a4f122cSGarrett D'Amore 		desc.acd_flags = MONCTL | AUDIO_CTRL_FLAG_MULTI;
6587a4f122cSGarrett D'Amore 		for (int i = 0; solo_insrcs[i]; i++) {
6597a4f122cSGarrett D'Amore 			desc.acd_enum[i] = solo_insrcs[i];
6607a4f122cSGarrett D'Amore 		}
6617a4f122cSGarrett D'Amore 		fn = solo_set_mixsrc;
6627a4f122cSGarrett D'Amore 		break;
6637a4f122cSGarrett D'Amore 
6647a4f122cSGarrett D'Amore 	case CTL_MICBOOST:
6657a4f122cSGarrett D'Amore 		desc.acd_name = AUDIO_CTRL_ID_MICBOOST;
6667a4f122cSGarrett D'Amore 		desc.acd_type = AUDIO_CTRL_TYPE_BOOLEAN;
6677a4f122cSGarrett D'Amore 		desc.acd_minvalue = 0;
6687a4f122cSGarrett D'Amore 		desc.acd_maxvalue = 1;
6697a4f122cSGarrett D'Amore 		desc.acd_flags = RECCTL;
6707a4f122cSGarrett D'Amore 		fn = solo_set_bool;
6717a4f122cSGarrett D'Amore 		break;
6727a4f122cSGarrett D'Amore 
6737a4f122cSGarrett D'Amore 	case CTL_LOOPBACK:
6747a4f122cSGarrett D'Amore 		desc.acd_name = AUDIO_CTRL_ID_LOOPBACK;
6757a4f122cSGarrett D'Amore 		desc.acd_type = AUDIO_CTRL_TYPE_BOOLEAN;
6767a4f122cSGarrett D'Amore 		desc.acd_minvalue = 0;
6777a4f122cSGarrett D'Amore 		desc.acd_maxvalue = 1;
6787a4f122cSGarrett D'Amore 		desc.acd_flags = RECCTL;
6797a4f122cSGarrett D'Amore 		fn = solo_set_bool;
6807a4f122cSGarrett D'Amore 		break;
6817a4f122cSGarrett D'Amore 
6827a4f122cSGarrett D'Amore 	case CTL_RECGAIN:
6837a4f122cSGarrett D'Amore 		desc.acd_name = AUDIO_CTRL_ID_RECGAIN;
6847a4f122cSGarrett D'Amore 		desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
6857a4f122cSGarrett D'Amore 		desc.acd_minvalue = 0;
6867a4f122cSGarrett D'Amore 		desc.acd_maxvalue = 100;
6877a4f122cSGarrett D'Amore 		desc.acd_flags = RECCTL;
6887a4f122cSGarrett D'Amore 		fn = solo_set_stereo;
6897a4f122cSGarrett D'Amore 		break;
6907a4f122cSGarrett D'Amore 	}
6917a4f122cSGarrett D'Amore 
6927a4f122cSGarrett D'Amore 	pc->val = val;
6937a4f122cSGarrett D'Amore 	pc->ctrl = audio_dev_add_control(dev->adev, &desc,
6947a4f122cSGarrett D'Amore 	    solo_get_value, fn, pc);
6957a4f122cSGarrett D'Amore }
6967a4f122cSGarrett D'Amore 
6977a4f122cSGarrett D'Amore static bool
solo_add_controls(solo_dev_t * dev)6987a4f122cSGarrett D'Amore solo_add_controls(solo_dev_t *dev)
6997a4f122cSGarrett D'Amore {
7007a4f122cSGarrett D'Amore 	solo_alloc_ctrl(dev, CTL_VOLUME, 0x4b);
7017a4f122cSGarrett D'Amore 	solo_alloc_ctrl(dev, CTL_FRONT, 0x5a5a);
7027a4f122cSGarrett D'Amore 	solo_alloc_ctrl(dev, CTL_SPEAKER, 0x4b);
7037a4f122cSGarrett D'Amore 	solo_alloc_ctrl(dev, CTL_MIC, 0x3232);
7047a4f122cSGarrett D'Amore 	solo_alloc_ctrl(dev, CTL_LINE, 0x4b4b);
7057a4f122cSGarrett D'Amore 	solo_alloc_ctrl(dev, CTL_CD, 0x4b4b);
7067a4f122cSGarrett D'Amore 	solo_alloc_ctrl(dev, CTL_AUX, 0);
7077a4f122cSGarrett D'Amore 	solo_alloc_ctrl(dev, CTL_MONO, 0);
7087a4f122cSGarrett D'Amore 	solo_alloc_ctrl(dev, CTL_RECSRC, (1U << INPUT_MIC));
7097a4f122cSGarrett D'Amore 	solo_alloc_ctrl(dev, CTL_MONSRC, 0);
7107a4f122cSGarrett D'Amore 	solo_alloc_ctrl(dev, CTL_RECGAIN, 0x4b4b);
7117a4f122cSGarrett D'Amore 	solo_alloc_ctrl(dev, CTL_MICBOOST, 1);
7127a4f122cSGarrett D'Amore 	solo_alloc_ctrl(dev, CTL_LOOPBACK, 0);
7137a4f122cSGarrett D'Amore 
7147a4f122cSGarrett D'Amore 	return (true);
7157a4f122cSGarrett D'Amore }
7167a4f122cSGarrett D'Amore 
7177a4f122cSGarrett D'Amore 
7187a4f122cSGarrett D'Amore /* utility functions for ESS */
7197a4f122cSGarrett D'Amore static uint8_t
solo_calcfilter(int spd)7207a4f122cSGarrett D'Amore solo_calcfilter(int spd)
7217a4f122cSGarrett D'Amore {
7227a4f122cSGarrett D'Amore 	int cutoff;
7237a4f122cSGarrett D'Amore 
7247a4f122cSGarrett D'Amore 	cutoff = (spd * 9 * 82) / 20;
7257a4f122cSGarrett D'Amore 	return (256 - (7160000 / cutoff));
7267a4f122cSGarrett D'Amore }
7277a4f122cSGarrett D'Amore 
7287a4f122cSGarrett D'Amore static void
solo_aud1_update(solo_engine_t * e)7297a4f122cSGarrett D'Amore solo_aud1_update(solo_engine_t *e)
7307a4f122cSGarrett D'Amore {
7317a4f122cSGarrett D'Amore 	solo_dev_t	*dev = e->dev;
7327a4f122cSGarrett D'Amore 	uint16_t	offset, n;
7337a4f122cSGarrett D'Amore 	uint32_t	ptr;
7347a4f122cSGarrett D'Amore 	uint32_t	count;
7357a4f122cSGarrett D'Amore 	uint32_t	diff;
736*68c47f65SGarrett D'Amore 	int		tries;
7377a4f122cSGarrett D'Amore 
7387a4f122cSGarrett D'Amore 	ASSERT(mutex_owned(&dev->mutex));
7397a4f122cSGarrett D'Amore 
7407a4f122cSGarrett D'Amore 	/*
7417a4f122cSGarrett D'Amore 	 * During recording, this register is known to give back
7427a4f122cSGarrett D'Amore 	 * garbage if it's not quiescent while being read.  This hack
743*68c47f65SGarrett D'Amore 	 * attempts to work around it.  We also suspend the DMA
744*68c47f65SGarrett D'Amore 	 * while we do this, to minimize record distortion.
7457a4f122cSGarrett D'Amore 	 */
746*68c47f65SGarrett D'Amore 	if (e->trigger) {
747*68c47f65SGarrett D'Amore 		drv_usecwait(20);
748*68c47f65SGarrett D'Amore 	}
749*68c47f65SGarrett D'Amore 	for (tries = 10; tries; tries--) {
750*68c47f65SGarrett D'Amore 		drv_usecwait(10);
751*68c47f65SGarrett D'Amore 		ptr = PORT_RD32(dev->vc, 0);
752*68c47f65SGarrett D'Amore 		count = PORT_RD16(dev->vc, 4);
753*68c47f65SGarrett D'Amore 		diff = e->paddr + SOLO_BUFSZ - ptr - count;
754*68c47f65SGarrett D'Amore 		if ((diff > 3) || (ptr < e->paddr) ||
755*68c47f65SGarrett D'Amore 		    (ptr >= (e->paddr + SOLO_BUFSZ))) {
756*68c47f65SGarrett D'Amore 			ptr = dev->last_capture;
757*68c47f65SGarrett D'Amore 		} else {
758*68c47f65SGarrett D'Amore 			break;
759*68c47f65SGarrett D'Amore 		}
760*68c47f65SGarrett D'Amore 	}
761*68c47f65SGarrett D'Amore 	if (e->trigger) {
762*68c47f65SGarrett D'Amore 		PORT_WR8(dev->vc, 0xf, 0);	/* restart DMA */
7637a4f122cSGarrett D'Amore 	}
764*68c47f65SGarrett D'Amore 	if (!tries) {
765*68c47f65SGarrett D'Amore 		/*
766*68c47f65SGarrett D'Amore 		 * Note, this is a pretty bad situation, because we'll
767*68c47f65SGarrett D'Amore 		 * not have an accurate idea of our position.  But its
768*68c47f65SGarrett D'Amore 		 * better than making a bad alteration.  If we had FMA
769*68c47f65SGarrett D'Amore 		 * for audio devices, this would be a good point to
770*68c47f65SGarrett D'Amore 		 * raise a fault.
771*68c47f65SGarrett D'Amore 		 */
772*68c47f65SGarrett D'Amore 		return;
773*68c47f65SGarrett D'Amore 	}
774*68c47f65SGarrett D'Amore 	dev->last_capture = ptr;
7757a4f122cSGarrett D'Amore 	offset = ptr - e->paddr;
7767a4f122cSGarrett D'Amore 	offset /= (SOLO_NCHAN * SOLO_SAMPSZ);
7777a4f122cSGarrett D'Amore 
7787a4f122cSGarrett D'Amore 	n = offset >= e->offset ?
7797a4f122cSGarrett D'Amore 	    offset - e->offset :
7807a4f122cSGarrett D'Amore 	    offset + SOLO_BUFSZ - e->offset;
7817a4f122cSGarrett D'Amore 
7827a4f122cSGarrett D'Amore 	e->offset = offset;
7837a4f122cSGarrett D'Amore 	e->count += n / (SOLO_NCHAN * SOLO_SAMPSZ);
7847a4f122cSGarrett D'Amore }
7857a4f122cSGarrett D'Amore 
7867a4f122cSGarrett D'Amore static void
solo_aud1_start(solo_engine_t * e)7877a4f122cSGarrett D'Amore solo_aud1_start(solo_engine_t *e)
7887a4f122cSGarrett D'Amore {
7897a4f122cSGarrett D'Amore 	solo_dev_t	*dev = e->dev;
7907a4f122cSGarrett D'Amore 	int		len;
7917a4f122cSGarrett D'Amore 	uint32_t	v;
7927a4f122cSGarrett D'Amore 
7937a4f122cSGarrett D'Amore 	ASSERT(mutex_owned(&dev->mutex));
7947a4f122cSGarrett D'Amore 
7957f8098edSGarrett D'Amore 	e->offset = 0;
7967a4f122cSGarrett D'Amore 	len = SOLO_FRAGSZ / 2;
7977a4f122cSGarrett D'Amore 	len = -len;
7987a4f122cSGarrett D'Amore 
7997a4f122cSGarrett D'Amore 	/* sample rate - 48 kHz */
8007a4f122cSGarrett D'Amore 	solo_write(dev, 0xa1, 0xf0);
8017a4f122cSGarrett D'Amore 	/* filter cutoff */
8027a4f122cSGarrett D'Amore 	solo_write(dev, 0xa2, solo_calcfilter(SOLO_RATE));
8037a4f122cSGarrett D'Amore 
8047a4f122cSGarrett D'Amore 
8057a4f122cSGarrett D'Amore 	/* mono/stereo - bit 0 set, bit 1 clear */
8067a4f122cSGarrett D'Amore 	solo_write(dev, 0xa8, (solo_read(dev, 0xa8) & ~0x03) | 1);
8077a4f122cSGarrett D'Amore 
8087a4f122cSGarrett D'Amore 	(void) solo_cmd(dev, 0xd3);	/* turn off DAC1 output */
8097a4f122cSGarrett D'Amore 
8107a4f122cSGarrett D'Amore 	/* setup fifo for signed 16-bit stereo */
8117a4f122cSGarrett D'Amore 	solo_write(dev, 0xb7, 0x71);
8127a4f122cSGarrett D'Amore 	solo_write(dev, 0xb7, 0xbc);
8137a4f122cSGarrett D'Amore 
8147a4f122cSGarrett D'Amore 	v = solo_mixer_scale(dev, CTL_RECGAIN);
8157a4f122cSGarrett D'Amore 	v = v | (v << 4);
8167a4f122cSGarrett D'Amore 	solo_write(dev, 0xb4, v & 0xff);
8177a4f122cSGarrett D'Amore 
8187a4f122cSGarrett D'Amore 	PORT_WR8(dev->vc, 0x8, 0xc4); /* command */
8197a4f122cSGarrett D'Amore 	PORT_WR8(dev->vc, 0xd, 0xff); /* clear DMA */
8207a4f122cSGarrett D'Amore 	PORT_WR8(dev->vc, 0xf, 0x01); /* stop DMA  */
8217a4f122cSGarrett D'Amore 
8227a4f122cSGarrett D'Amore 	PORT_WR8(dev->vc, 0xd, 0xff); /* reset */
8237a4f122cSGarrett D'Amore 	PORT_WR8(dev->vc, 0xf, 0x01); /* mask */
8247a4f122cSGarrett D'Amore 	PORT_WR8(dev->vc, 0xb, 0x14); /* mode */
8257a4f122cSGarrett D'Amore 
8267a4f122cSGarrett D'Amore 	PORT_WR32(dev->vc, 0x0, e->paddr);
8277a4f122cSGarrett D'Amore 	PORT_WR16(dev->vc, 0x4, SOLO_BUFSZ - 1);
8287a4f122cSGarrett D'Amore 
8297a4f122cSGarrett D'Amore 	/* transfer length low, high */
8307a4f122cSGarrett D'Amore 	solo_write(dev, 0xa4, len & 0x00ff);
8317a4f122cSGarrett D'Amore 	solo_write(dev, 0xa5, (len & 0xff00) >> 8);
8327a4f122cSGarrett D'Amore 
8337a4f122cSGarrett D'Amore 	/* autoinit, dma dir, go for it */
8347a4f122cSGarrett D'Amore 	solo_write(dev, 0xb8, 0x0f);
8357a4f122cSGarrett D'Amore 	PORT_WR8(dev->vc, 0xf, 0);	/* start DMA */
8367a4f122cSGarrett D'Amore 
8377a4f122cSGarrett D'Amore 	dev->last_capture = e->paddr;
838*68c47f65SGarrett D'Amore 	e->trigger = true;
8397a4f122cSGarrett D'Amore }
8407a4f122cSGarrett D'Amore 
8417a4f122cSGarrett D'Amore static void
solo_aud1_stop(solo_engine_t * e)8427a4f122cSGarrett D'Amore solo_aud1_stop(solo_engine_t *e)
8437a4f122cSGarrett D'Amore {
8447a4f122cSGarrett D'Amore 	solo_dev_t	*dev = e->dev;
8457a4f122cSGarrett D'Amore 
8467a4f122cSGarrett D'Amore 	/* NB: We might be in quiesce, without a lock held */
8477a4f122cSGarrett D'Amore 	solo_write(dev, 0xb8, solo_read(dev, 0xb8) & ~0x01);
848*68c47f65SGarrett D'Amore 	e->trigger = false;
8497a4f122cSGarrett D'Amore }
8507a4f122cSGarrett D'Amore 
8517a4f122cSGarrett D'Amore static void
solo_aud2_update(solo_engine_t * e)8527a4f122cSGarrett D'Amore solo_aud2_update(solo_engine_t *e)
8537a4f122cSGarrett D'Amore {
8547a4f122cSGarrett D'Amore 	solo_dev_t	*dev = e->dev;
8557a4f122cSGarrett D'Amore 	uint16_t	offset = 0, n;
8567a4f122cSGarrett D'Amore 
8577a4f122cSGarrett D'Amore 	ASSERT(mutex_owned(&dev->mutex));
8587a4f122cSGarrett D'Amore 
8597a4f122cSGarrett D'Amore 	offset = SOLO_BUFSZ - PORT_RD16(dev->io, 0x4);
8607a4f122cSGarrett D'Amore 	offset /= (SOLO_NCHAN * SOLO_SAMPSZ);
8617a4f122cSGarrett D'Amore 
8627a4f122cSGarrett D'Amore 	n = offset >= e->offset ?
8637a4f122cSGarrett D'Amore 	    offset - e->offset :
8647a4f122cSGarrett D'Amore 	    offset + SOLO_BUFFR - e->offset;
8657a4f122cSGarrett D'Amore 
8667a4f122cSGarrett D'Amore 	e->offset = offset;
8677a4f122cSGarrett D'Amore 	e->count += n;
8687a4f122cSGarrett D'Amore }
8697a4f122cSGarrett D'Amore 
8707a4f122cSGarrett D'Amore static void
solo_aud2_start(solo_engine_t * e)8717a4f122cSGarrett D'Amore solo_aud2_start(solo_engine_t *e)
8727a4f122cSGarrett D'Amore {
8737a4f122cSGarrett D'Amore 	solo_dev_t	*dev = e->dev;
8747a4f122cSGarrett D'Amore 	int		len;
8757a4f122cSGarrett D'Amore 	uint32_t	v;
8767a4f122cSGarrett D'Amore 
8777a4f122cSGarrett D'Amore 	ASSERT(mutex_owned(&dev->mutex));
8787a4f122cSGarrett D'Amore 
8797f8098edSGarrett D'Amore 	e->offset = 0;
8807a4f122cSGarrett D'Amore 	len = SOLO_FRAGSZ / 2;
8817a4f122cSGarrett D'Amore 	len = -len;
8827a4f122cSGarrett D'Amore 
8837a4f122cSGarrett D'Amore 	/* program transfer type */
8847a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x78, 0x10);
8857a4f122cSGarrett D'Amore 	/* sample rate - 48 kHz */
8867a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x70, 0xf0);
8877a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x72, solo_calcfilter(SOLO_RATE));
8887a4f122cSGarrett D'Amore 	/* transfer length low & high */
8897a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x74, len & 0x00ff);
8907a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x76, (len & 0xff00) >> 8);
8917a4f122cSGarrett D'Amore 	/* enable irq, set signed 16-bit stereo format */
8927a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x7a, 0x47);
8937a4f122cSGarrett D'Amore 
8947a4f122cSGarrett D'Amore 	PORT_WR8(dev->io, 0x6, 0);
8957a4f122cSGarrett D'Amore 	PORT_WR32(dev->io, 0x0, e->paddr);
8967a4f122cSGarrett D'Amore 	PORT_WR16(dev->io, 0x4, SOLO_BUFSZ);
8977a4f122cSGarrett D'Amore 
8987a4f122cSGarrett D'Amore 	/* this crazy initialization appears to help with fifo weirdness */
8997a4f122cSGarrett D'Amore 	/* start the engine running */
9007a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x78, 0x92);
9017a4f122cSGarrett D'Amore 	drv_usecwait(10);
9027a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x78, 0x93);
9037a4f122cSGarrett D'Amore 
9047a4f122cSGarrett D'Amore 	PORT_WR8(dev->io, 0x6, 0x0a); /* autoinit, enable */
9057a4f122cSGarrett D'Amore 
9067a4f122cSGarrett D'Amore 	v = solo_mixer_scale(dev, CTL_VOLUME);
9077a4f122cSGarrett D'Amore 	v = v | (v << 4);
9087a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x7c, v & 0xff);
909*68c47f65SGarrett D'Amore 
910*68c47f65SGarrett D'Amore 	e->trigger = true;
9117a4f122cSGarrett D'Amore }
9127a4f122cSGarrett D'Amore 
9137a4f122cSGarrett D'Amore static void
solo_aud2_stop(solo_engine_t * e)9147a4f122cSGarrett D'Amore solo_aud2_stop(solo_engine_t *e)
9157a4f122cSGarrett D'Amore {
9167a4f122cSGarrett D'Amore 	solo_dev_t	*dev = e->dev;
9177a4f122cSGarrett D'Amore 
9187a4f122cSGarrett D'Amore 	/* NB: We might be in quiesce, without a lock held */
9197a4f122cSGarrett D'Amore 	PORT_WR8(dev->io, 0x6, 0);
9207a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x78, solo_getmixer(dev, 0x78) & ~0x03);
921*68c47f65SGarrett D'Amore 
922*68c47f65SGarrett D'Amore 	e->trigger = false;
9237a4f122cSGarrett D'Amore }
9247a4f122cSGarrett D'Amore 
9257a4f122cSGarrett D'Amore /*
9267a4f122cSGarrett D'Amore  * Audio entry points.
9277a4f122cSGarrett D'Amore  */
9287a4f122cSGarrett D'Amore static int
solo_format(void * arg)9297a4f122cSGarrett D'Amore solo_format(void *arg)
9307a4f122cSGarrett D'Amore {
9317a4f122cSGarrett D'Amore 	solo_engine_t	*e = arg;
9327a4f122cSGarrett D'Amore 	return (e->format);
9337a4f122cSGarrett D'Amore }
9347a4f122cSGarrett D'Amore 
9357a4f122cSGarrett D'Amore static int
solo_channels(void * arg)9367a4f122cSGarrett D'Amore solo_channels(void *arg)
9377a4f122cSGarrett D'Amore {
9387a4f122cSGarrett D'Amore 	_NOTE(ARGUNUSED(arg));
9397a4f122cSGarrett D'Amore 	return (SOLO_NCHAN);
9407a4f122cSGarrett D'Amore }
9417a4f122cSGarrett D'Amore 
9427a4f122cSGarrett D'Amore static int
solo_rate(void * arg)9437a4f122cSGarrett D'Amore solo_rate(void *arg)
9447a4f122cSGarrett D'Amore {
9457a4f122cSGarrett D'Amore 	_NOTE(ARGUNUSED(arg));
9467a4f122cSGarrett D'Amore 	return (SOLO_RATE);
9477a4f122cSGarrett D'Amore }
9487a4f122cSGarrett D'Amore 
9497a4f122cSGarrett D'Amore static void
solo_chinfo(void * arg,int chan,unsigned * offset,unsigned * incr)9507a4f122cSGarrett D'Amore solo_chinfo(void *arg, int chan, unsigned *offset, unsigned *incr)
9517a4f122cSGarrett D'Amore {
9527a4f122cSGarrett D'Amore 	solo_engine_t *e = arg;
9537a4f122cSGarrett D'Amore 
9547a4f122cSGarrett D'Amore 	if (e->swapped) {
9557a4f122cSGarrett D'Amore 		*offset = !chan;
9567a4f122cSGarrett D'Amore 	} else {
9577a4f122cSGarrett D'Amore 		*offset = chan;
9587a4f122cSGarrett D'Amore 	}
9597a4f122cSGarrett D'Amore 	*incr = 2;
9607a4f122cSGarrett D'Amore }
9617a4f122cSGarrett D'Amore 
9627a4f122cSGarrett D'Amore static void
solo_sync(void * arg,unsigned nframes)9637a4f122cSGarrett D'Amore solo_sync(void *arg, unsigned nframes)
9647a4f122cSGarrett D'Amore {
9657a4f122cSGarrett D'Amore 	solo_engine_t *e = arg;
9667a4f122cSGarrett D'Amore 
9677a4f122cSGarrett D'Amore 	_NOTE(ARGUNUSED(nframes));
9687a4f122cSGarrett D'Amore 
9697a4f122cSGarrett D'Amore 	(void) ddi_dma_sync(e->dmah, 0, 0, e->syncdir);
9707a4f122cSGarrett D'Amore }
9717a4f122cSGarrett D'Amore 
9727a4f122cSGarrett D'Amore 
9737a4f122cSGarrett D'Amore static uint64_t
solo_count(void * arg)9747a4f122cSGarrett D'Amore solo_count(void *arg)
9757a4f122cSGarrett D'Amore {
9767a4f122cSGarrett D'Amore 	solo_engine_t	*e = arg;
9777a4f122cSGarrett D'Amore 	solo_dev_t	*dev = e->dev;
9787a4f122cSGarrett D'Amore 	uint64_t	count;
9797a4f122cSGarrett D'Amore 
9807a4f122cSGarrett D'Amore 	mutex_enter(&dev->mutex);
981*68c47f65SGarrett D'Amore 	e->update(e);
9827a4f122cSGarrett D'Amore 	count = e->count;
9837a4f122cSGarrett D'Amore 	mutex_exit(&dev->mutex);
9847a4f122cSGarrett D'Amore 
9857a4f122cSGarrett D'Amore 	return (count);
9867a4f122cSGarrett D'Amore }
9877a4f122cSGarrett D'Amore 
9887a4f122cSGarrett D'Amore static int
solo_open(void * arg,int f,unsigned * nframes,caddr_t * buf)989*68c47f65SGarrett D'Amore solo_open(void *arg, int f, unsigned *nframes, caddr_t *buf)
9907a4f122cSGarrett D'Amore {
9917a4f122cSGarrett D'Amore 	solo_engine_t	*e = arg;
9927a4f122cSGarrett D'Amore 	solo_dev_t	*dev = e->dev;
9937a4f122cSGarrett D'Amore 
9947a4f122cSGarrett D'Amore 	_NOTE(ARGUNUSED(f));
9957a4f122cSGarrett D'Amore 
996*68c47f65SGarrett D'Amore 	*nframes = SOLO_NFRAGS * SOLO_FRAGFR;
9977a4f122cSGarrett D'Amore 	*buf = e->kaddr;
9987a4f122cSGarrett D'Amore 
9997a4f122cSGarrett D'Amore 	mutex_enter(&dev->mutex);
10007a4f122cSGarrett D'Amore 	e->started = false;
10017a4f122cSGarrett D'Amore 	e->count = 0;
10027a4f122cSGarrett D'Amore 	mutex_exit(&dev->mutex);
10037a4f122cSGarrett D'Amore 
10047a4f122cSGarrett D'Amore 	return (0);
10057a4f122cSGarrett D'Amore }
10067a4f122cSGarrett D'Amore 
10077a4f122cSGarrett D'Amore void
solo_close(void * arg)10087a4f122cSGarrett D'Amore solo_close(void *arg)
10097a4f122cSGarrett D'Amore {
10107a4f122cSGarrett D'Amore 	solo_engine_t	*e = arg;
10117a4f122cSGarrett D'Amore 	solo_dev_t	*dev = e->dev;
10127a4f122cSGarrett D'Amore 
10137a4f122cSGarrett D'Amore 	mutex_enter(&dev->mutex);
1014*68c47f65SGarrett D'Amore 	e->stop(e);
10157a4f122cSGarrett D'Amore 	e->started = false;
10167a4f122cSGarrett D'Amore 	mutex_exit(&dev->mutex);
10177a4f122cSGarrett D'Amore }
10187a4f122cSGarrett D'Amore 
10197a4f122cSGarrett D'Amore 
10207a4f122cSGarrett D'Amore static int
solo_start(void * arg)10217a4f122cSGarrett D'Amore solo_start(void *arg)
10227a4f122cSGarrett D'Amore {
10237a4f122cSGarrett D'Amore 	solo_engine_t	*e = arg;
10247a4f122cSGarrett D'Amore 	solo_dev_t	*dev = e->dev;
10257a4f122cSGarrett D'Amore 
10267a4f122cSGarrett D'Amore 	mutex_enter(&dev->mutex);
10277a4f122cSGarrett D'Amore 	if (!e->started) {
1028*68c47f65SGarrett D'Amore 		e->start(e);
10297a4f122cSGarrett D'Amore 		e->started = true;
10307a4f122cSGarrett D'Amore 	}
10317a4f122cSGarrett D'Amore 	mutex_exit(&dev->mutex);
10327a4f122cSGarrett D'Amore 
10337a4f122cSGarrett D'Amore 	return (0);
10347a4f122cSGarrett D'Amore }
10357a4f122cSGarrett D'Amore 
10367a4f122cSGarrett D'Amore static void
solo_stop(void * arg)10377a4f122cSGarrett D'Amore solo_stop(void *arg)
10387a4f122cSGarrett D'Amore {
10397a4f122cSGarrett D'Amore 	solo_engine_t	*e = arg;
10407a4f122cSGarrett D'Amore 	solo_dev_t	*dev = e->dev;
10417a4f122cSGarrett D'Amore 
10427a4f122cSGarrett D'Amore 	mutex_enter(&dev->mutex);
10437a4f122cSGarrett D'Amore 	if (e->started) {
1044*68c47f65SGarrett D'Amore 		e->stop(e);
10457a4f122cSGarrett D'Amore 		e->started = false;
10467a4f122cSGarrett D'Amore 	}
10477a4f122cSGarrett D'Amore 	mutex_exit(&dev->mutex);
10487a4f122cSGarrett D'Amore 
10497a4f122cSGarrett D'Amore }
10507a4f122cSGarrett D'Amore 
10517a4f122cSGarrett D'Amore static audio_engine_ops_t solo_engine_ops = {
10527a4f122cSGarrett D'Amore 	AUDIO_ENGINE_VERSION,
10537a4f122cSGarrett D'Amore 	solo_open,
10547a4f122cSGarrett D'Amore 	solo_close,
10557a4f122cSGarrett D'Amore 	solo_start,
10567a4f122cSGarrett D'Amore 	solo_stop,
10577a4f122cSGarrett D'Amore 	solo_count,
10587a4f122cSGarrett D'Amore 	solo_format,
10597a4f122cSGarrett D'Amore 	solo_channels,
10607a4f122cSGarrett D'Amore 	solo_rate,
10617a4f122cSGarrett D'Amore 	solo_sync,
1062f9ead4a5SGarrett D'Amore 	NULL,
10637a4f122cSGarrett D'Amore 	solo_chinfo,
1064f9ead4a5SGarrett D'Amore 	NULL,
10657a4f122cSGarrett D'Amore };
10667a4f122cSGarrett D'Amore 
10677a4f122cSGarrett D'Amore static void
solo_release_resources(solo_dev_t * dev)10687a4f122cSGarrett D'Amore solo_release_resources(solo_dev_t *dev)
10697a4f122cSGarrett D'Amore {
10707a4f122cSGarrett D'Amore 	if (dev->ihandle != NULL) {
10717a4f122cSGarrett D'Amore 		(void) ddi_intr_disable(dev->ihandle);
10727a4f122cSGarrett D'Amore 		(void) ddi_intr_remove_handler(dev->ihandle);
10737a4f122cSGarrett D'Amore 		(void) ddi_intr_free(dev->ihandle);
10747a4f122cSGarrett D'Amore 		mutex_destroy(&dev->mutex);
10757a4f122cSGarrett D'Amore 	}
10767a4f122cSGarrett D'Amore 
10777a4f122cSGarrett D'Amore 	if (dev->io.acch != NULL) {
10787a4f122cSGarrett D'Amore 		ddi_regs_map_free(&dev->io.acch);
10797a4f122cSGarrett D'Amore 	}
10807a4f122cSGarrett D'Amore 
10817a4f122cSGarrett D'Amore 	if (dev->sb.acch != NULL) {
10827a4f122cSGarrett D'Amore 		ddi_regs_map_free(&dev->sb.acch);
10837a4f122cSGarrett D'Amore 	}
10847a4f122cSGarrett D'Amore 
10857a4f122cSGarrett D'Amore 	if (dev->vc.acch != NULL) {
10867a4f122cSGarrett D'Amore 		ddi_regs_map_free(&dev->vc.acch);
10877a4f122cSGarrett D'Amore 	}
10887a4f122cSGarrett D'Amore 
10897a4f122cSGarrett D'Amore 	if (dev->pcih != NULL) {
10907a4f122cSGarrett D'Amore 		pci_config_teardown(&dev->pcih);
10917a4f122cSGarrett D'Amore 	}
10927a4f122cSGarrett D'Amore 
10937a4f122cSGarrett D'Amore 	/* release play resources */
10947a4f122cSGarrett D'Amore 	if (dev->play.paddr != 0)
10957a4f122cSGarrett D'Amore 		(void) ddi_dma_unbind_handle(dev->play.dmah);
10967a4f122cSGarrett D'Amore 	if (dev->play.acch != NULL)
10977a4f122cSGarrett D'Amore 		ddi_dma_mem_free(&dev->play.acch);
10987a4f122cSGarrett D'Amore 	if (dev->play.dmah != NULL)
10997a4f122cSGarrett D'Amore 		ddi_dma_free_handle(&dev->play.dmah);
11007a4f122cSGarrett D'Amore 
11017a4f122cSGarrett D'Amore 	if (dev->play.engine != NULL) {
11027a4f122cSGarrett D'Amore 		audio_dev_remove_engine(dev->adev, dev->play.engine);
11037a4f122cSGarrett D'Amore 		audio_engine_free(dev->play.engine);
11047a4f122cSGarrett D'Amore 	}
11057a4f122cSGarrett D'Amore 
11067a4f122cSGarrett D'Amore 	/* release record resources */
11077a4f122cSGarrett D'Amore 	if (dev->rec.paddr != 0)
11087a4f122cSGarrett D'Amore 		(void) ddi_dma_unbind_handle(dev->rec.dmah);
11097a4f122cSGarrett D'Amore 	if (dev->rec.acch != NULL)
11107a4f122cSGarrett D'Amore 		ddi_dma_mem_free(&dev->rec.acch);
11117a4f122cSGarrett D'Amore 	if (dev->rec.dmah != NULL)
11127a4f122cSGarrett D'Amore 		ddi_dma_free_handle(&dev->rec.dmah);
11137a4f122cSGarrett D'Amore 
11147a4f122cSGarrett D'Amore 	if (dev->rec.engine != NULL) {
11157a4f122cSGarrett D'Amore 		audio_dev_remove_engine(dev->adev, dev->rec.engine);
11167a4f122cSGarrett D'Amore 		audio_engine_free(dev->rec.engine);
11177a4f122cSGarrett D'Amore 	}
11187a4f122cSGarrett D'Amore 
11197f8098edSGarrett D'Amore 	for (int i = 0; i < CTL_NUM; i++) {
11207f8098edSGarrett D'Amore 		if (dev->ctrls[i].ctrl != NULL) {
11217f8098edSGarrett D'Amore 			audio_dev_del_control(dev->ctrls[i].ctrl);
11227f8098edSGarrett D'Amore 		}
11237f8098edSGarrett D'Amore 	}
11247f8098edSGarrett D'Amore 
11257a4f122cSGarrett D'Amore 	if (dev->adev != NULL) {
11267a4f122cSGarrett D'Amore 		audio_dev_free(dev->adev);
11277a4f122cSGarrett D'Amore 	}
11287a4f122cSGarrett D'Amore 
11297a4f122cSGarrett D'Amore 	kmem_free(dev, sizeof (*dev));
11307a4f122cSGarrett D'Amore }
11317a4f122cSGarrett D'Amore 
11327a4f122cSGarrett D'Amore static bool
solo_setup_interrupts(solo_dev_t * dev)11337a4f122cSGarrett D'Amore solo_setup_interrupts(solo_dev_t *dev)
11347a4f122cSGarrett D'Amore {
11357a4f122cSGarrett D'Amore 	int actual;
11367a4f122cSGarrett D'Amore 	uint_t ipri;
11377a4f122cSGarrett D'Amore 
11387a4f122cSGarrett D'Amore 	if ((ddi_intr_alloc(dev->dip, &dev->ihandle, DDI_INTR_TYPE_FIXED,
11397a4f122cSGarrett D'Amore 	    0, 1, &actual, DDI_INTR_ALLOC_NORMAL) != DDI_SUCCESS) ||
11407a4f122cSGarrett D'Amore 	    (actual != 1)) {
11417a4f122cSGarrett D'Amore 		audio_dev_warn(dev->adev, "can't alloc intr handle");
11427a4f122cSGarrett D'Amore 		return (false);
11437a4f122cSGarrett D'Amore 	}
11447a4f122cSGarrett D'Amore 
11457a4f122cSGarrett D'Amore 	if (ddi_intr_get_pri(dev->ihandle, &ipri) != DDI_SUCCESS) {
11467a4f122cSGarrett D'Amore 		audio_dev_warn(dev->adev,  "can't determine intr priority");
11477a4f122cSGarrett D'Amore 		(void) ddi_intr_free(dev->ihandle);
11487a4f122cSGarrett D'Amore 		dev->ihandle = NULL;
11497a4f122cSGarrett D'Amore 		return (false);
11507a4f122cSGarrett D'Amore 	}
11517a4f122cSGarrett D'Amore 
11527a4f122cSGarrett D'Amore 	if (ddi_intr_add_handler(dev->ihandle, solo_intr, dev,
11537a4f122cSGarrett D'Amore 	    NULL) != DDI_SUCCESS) {
11547a4f122cSGarrett D'Amore 		audio_dev_warn(dev->adev, "can't add intr handler");
11557a4f122cSGarrett D'Amore 		(void) ddi_intr_free(dev->ihandle);
11567a4f122cSGarrett D'Amore 		dev->ihandle = NULL;
11577a4f122cSGarrett D'Amore 		return (false);
11587a4f122cSGarrett D'Amore 	}
11597a4f122cSGarrett D'Amore 
11607a4f122cSGarrett D'Amore 	mutex_init(&dev->mutex, NULL, MUTEX_DRIVER, DDI_INTR_PRI(ipri));
11617a4f122cSGarrett D'Amore 
11627a4f122cSGarrett D'Amore 	return (true);
11637a4f122cSGarrett D'Amore }
11647a4f122cSGarrett D'Amore 
11657a4f122cSGarrett D'Amore static bool
solo_map_registers(solo_dev_t * dev)11667a4f122cSGarrett D'Amore solo_map_registers(solo_dev_t *dev)
11677a4f122cSGarrett D'Amore {
11687a4f122cSGarrett D'Amore 	dev_info_t	*dip = dev->dip;
11697a4f122cSGarrett D'Amore 
11707a4f122cSGarrett D'Amore 	/* map registers */
11717a4f122cSGarrett D'Amore 	if (ddi_regs_map_setup(dip, 1, &dev->io.base, 0, 0, &acc_attr,
11727a4f122cSGarrett D'Amore 	    &dev->io.acch) != DDI_SUCCESS) {
11737a4f122cSGarrett D'Amore 		audio_dev_warn(dev->adev, "can't map IO registers");
11747a4f122cSGarrett D'Amore 		return (false);
11757a4f122cSGarrett D'Amore 	}
11767a4f122cSGarrett D'Amore 	if (ddi_regs_map_setup(dip, 2, &dev->sb.base, 0, 0, &acc_attr,
11777a4f122cSGarrett D'Amore 	    &dev->sb.acch) != DDI_SUCCESS) {
11787a4f122cSGarrett D'Amore 		audio_dev_warn(dev->adev, "can't map SB registers");
11797a4f122cSGarrett D'Amore 		return (false);
11807a4f122cSGarrett D'Amore 	}
11817a4f122cSGarrett D'Amore 	if (ddi_regs_map_setup(dip, 3, &dev->vc.base, 0, 0, &acc_attr,
11827a4f122cSGarrett D'Amore 	    &dev->vc.acch) != DDI_SUCCESS) {
11837a4f122cSGarrett D'Amore 		audio_dev_warn(dev->adev, "can't map VC registers");
11847a4f122cSGarrett D'Amore 		return (false);
11857a4f122cSGarrett D'Amore 	}
11867a4f122cSGarrett D'Amore 
11877a4f122cSGarrett D'Amore 	return (true);
11887a4f122cSGarrett D'Amore }
11897a4f122cSGarrett D'Amore 
11907a4f122cSGarrett D'Amore #define	ESS_PCI_LEGACYCONTROL		0x40
11917a4f122cSGarrett D'Amore #define	ESS_PCI_CONFIG			0x50
11927a4f122cSGarrett D'Amore #define	ESS_PCI_DDMACONTROL		0x60
11937a4f122cSGarrett D'Amore 
11947a4f122cSGarrett D'Amore static bool
solo_init_hw(solo_dev_t * dev)11957a4f122cSGarrett D'Amore solo_init_hw(solo_dev_t *dev)
11967a4f122cSGarrett D'Amore {
11977a4f122cSGarrett D'Amore 	uint32_t	data;
11987a4f122cSGarrett D'Amore 
11997a4f122cSGarrett D'Amore 	/*
12007a4f122cSGarrett D'Amore 	 * Legacy audio register -- disable legacy audio.  We also
12017a4f122cSGarrett D'Amore 	 * arrange for 16-bit I/O address decoding.
12027a4f122cSGarrett D'Amore 	 */
12037a4f122cSGarrett D'Amore 	/* this version disables the MPU, FM synthesis (Adlib), and Game Port */
12047a4f122cSGarrett D'Amore 	pci_config_put16(dev->pcih, ESS_PCI_LEGACYCONTROL, 0x8041);
12057a4f122cSGarrett D'Amore 
12067a4f122cSGarrett D'Amore 	/*
12077a4f122cSGarrett D'Amore 	 * Note that Solo-1 uses I/O space for all BARs, and hardwires
12087a4f122cSGarrett D'Amore 	 * the upper 32-bits to zero.
12097a4f122cSGarrett D'Amore 	 */
12107a4f122cSGarrett D'Amore 	data = pci_config_get32(dev->pcih, PCI_CONF_BASE2);
12117a4f122cSGarrett D'Amore 	data |= 1;
12127a4f122cSGarrett D'Amore 	pci_config_put16(dev->pcih, ESS_PCI_DDMACONTROL, data & 0xffff);
12137a4f122cSGarrett D'Amore 
12147a4f122cSGarrett D'Amore 	/*
12157a4f122cSGarrett D'Amore 	 * Make sure that legacy IRQ and DRQ are disbled.  We disable most
12167a4f122cSGarrett D'Amore 	 * other legacy features too.
12177a4f122cSGarrett D'Amore 	 */
12187a4f122cSGarrett D'Amore 	pci_config_put16(dev->pcih, ESS_PCI_CONFIG, 0);
12197a4f122cSGarrett D'Amore 
12207a4f122cSGarrett D'Amore 	if (!solo_reset_dsp(dev))
12217a4f122cSGarrett D'Amore 		return (false);
12227a4f122cSGarrett D'Amore 
12237a4f122cSGarrett D'Amore 	/* enable extended mode */
12247a4f122cSGarrett D'Amore 	(void) solo_cmd(dev, 0xc6);
12257a4f122cSGarrett D'Amore 
12267a4f122cSGarrett D'Amore 
12277a4f122cSGarrett D'Amore 	PORT_WR8(dev->io, 0x7, 0x30); /* enable audio irqs */
12287a4f122cSGarrett D'Amore 
12297a4f122cSGarrett D'Amore 	/* demand mode, 4 bytes/xfer */
12307a4f122cSGarrett D'Amore 	solo_write(dev, 0xb9, 0x01);
12317a4f122cSGarrett D'Amore 
12327a4f122cSGarrett D'Amore 	/*
12337a4f122cSGarrett D'Amore 	 * This sets Audio 2 (playback) to use its own independent
12347a4f122cSGarrett D'Amore 	 * rate control, and gives us 48 kHz compatible divisors.  It
12357a4f122cSGarrett D'Amore 	 * also bypasses the switched capacitor filter.
12367a4f122cSGarrett D'Amore 	 */
12377a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0x71, 0x2a);
12387a4f122cSGarrett D'Amore 
12397a4f122cSGarrett D'Amore 	/* irq control */
12407a4f122cSGarrett D'Amore 	solo_write(dev, 0xb1, (solo_read(dev, 0xb1) & 0x0f) | 0x50);
12417a4f122cSGarrett D'Amore 	/* drq control */
12427a4f122cSGarrett D'Amore 	solo_write(dev, 0xb2, (solo_read(dev, 0xb2) & 0x0f) | 0x50);
12437a4f122cSGarrett D'Amore 
12447a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0, 0); /* reset mixer settings */
12457a4f122cSGarrett D'Amore 
12467a4f122cSGarrett D'Amore 	solo_configure_mixer(dev);
12477a4f122cSGarrett D'Amore 	return (true);
12487a4f122cSGarrett D'Amore }
12497a4f122cSGarrett D'Amore 
12507a4f122cSGarrett D'Amore static bool
solo_alloc_engine(solo_dev_t * dev,int engno)12517a4f122cSGarrett D'Amore solo_alloc_engine(solo_dev_t *dev, int engno)
12527a4f122cSGarrett D'Amore {
12537a4f122cSGarrett D'Amore 	size_t			rlen;
12547a4f122cSGarrett D'Amore 	ddi_dma_attr_t		*dattr;
12557a4f122cSGarrett D'Amore 	ddi_dma_cookie_t	c;
12567a4f122cSGarrett D'Amore 	unsigned		ccnt;
12577a4f122cSGarrett D'Amore 	unsigned		caps;
12587a4f122cSGarrett D'Amore 	unsigned		dflags;
12597a4f122cSGarrett D'Amore 	const char		*desc;
12607a4f122cSGarrett D'Amore 	solo_engine_t		*e;
12617a4f122cSGarrett D'Amore 
12627a4f122cSGarrett D'Amore 	ASSERT((engno == 1) || (engno = 2));
12637a4f122cSGarrett D'Amore 
12647a4f122cSGarrett D'Amore 	switch (engno) {
12657a4f122cSGarrett D'Amore 	case 1:	/* record */
12667a4f122cSGarrett D'Amore 		e = &dev->rec;
12677a4f122cSGarrett D'Amore 		desc = "record";
12687a4f122cSGarrett D'Amore 		dattr = &dma_attr_audio1;
12697a4f122cSGarrett D'Amore 		caps = ENGINE_INPUT_CAP;
12707a4f122cSGarrett D'Amore 		dflags = DDI_DMA_READ | DDI_DMA_CONSISTENT;
12717a4f122cSGarrett D'Amore 		e->syncdir = DDI_DMA_SYNC_FORKERNEL;
12727a4f122cSGarrett D'Amore 		e->update = solo_aud1_update;
12737a4f122cSGarrett D'Amore 		e->start = solo_aud1_start;
12747a4f122cSGarrett D'Amore 		e->stop = solo_aud1_stop;
12757a4f122cSGarrett D'Amore 		e->format = AUDIO_FORMAT_S16_BE;
12767a4f122cSGarrett D'Amore 		e->swapped = true;
12777a4f122cSGarrett D'Amore 		break;
12787a4f122cSGarrett D'Amore 
12797a4f122cSGarrett D'Amore 	case 2:	/* playback */
12807a4f122cSGarrett D'Amore 		e = &dev->play;
12817a4f122cSGarrett D'Amore 		desc = "playback";
12827a4f122cSGarrett D'Amore 		dattr = &dma_attr_audio2;
12837a4f122cSGarrett D'Amore 		caps = ENGINE_OUTPUT_CAP;
12847a4f122cSGarrett D'Amore 		dflags = DDI_DMA_WRITE | DDI_DMA_CONSISTENT;
12857a4f122cSGarrett D'Amore 		e->syncdir = DDI_DMA_SYNC_FORDEV;
12867a4f122cSGarrett D'Amore 		e->update = solo_aud2_update;
12877a4f122cSGarrett D'Amore 		e->start = solo_aud2_start;
12887a4f122cSGarrett D'Amore 		e->stop = solo_aud2_stop;
12897a4f122cSGarrett D'Amore 		e->format = AUDIO_FORMAT_S16_LE;
12907a4f122cSGarrett D'Amore 		e->swapped = false;
12917a4f122cSGarrett D'Amore 		break;
12927a4f122cSGarrett D'Amore 
12937a4f122cSGarrett D'Amore 	default:
12947a4f122cSGarrett D'Amore 		audio_dev_warn(dev->adev, "bad engine number!");
12957a4f122cSGarrett D'Amore 		return (false);
12967a4f122cSGarrett D'Amore 	}
12977a4f122cSGarrett D'Amore 
12987a4f122cSGarrett D'Amore 	e->dev = dev;
12997a4f122cSGarrett D'Amore 
13007a4f122cSGarrett D'Amore 	if (ddi_dma_alloc_handle(dev->dip, dattr, DDI_DMA_SLEEP, NULL,
13017a4f122cSGarrett D'Amore 	    &e->dmah) != DDI_SUCCESS) {
13027a4f122cSGarrett D'Amore 		audio_dev_warn(dev->adev, "%s dma handle alloc failed", desc);
13037a4f122cSGarrett D'Amore 		return (false);
13047a4f122cSGarrett D'Amore 	}
13057a4f122cSGarrett D'Amore 	if (ddi_dma_mem_alloc(e->dmah, SOLO_BUFSZ, &buf_attr,
13067a4f122cSGarrett D'Amore 	    DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &e->kaddr,
13077a4f122cSGarrett D'Amore 	    &rlen, &e->acch) != DDI_SUCCESS) {
13087a4f122cSGarrett D'Amore 		audio_dev_warn(dev->adev, "%s dma memory alloc failed", desc);
13097a4f122cSGarrett D'Amore 		return (false);
13107a4f122cSGarrett D'Amore 	}
13117a4f122cSGarrett D'Amore 	/* ensure that the buffer is zeroed out properly */
13127a4f122cSGarrett D'Amore 	bzero(e->kaddr, rlen);
13137a4f122cSGarrett D'Amore 	if (ddi_dma_addr_bind_handle(e->dmah, NULL, e->kaddr, SOLO_BUFSZ,
13147a4f122cSGarrett D'Amore 	    dflags, DDI_DMA_SLEEP, NULL, &c, &ccnt) != DDI_DMA_MAPPED) {
13157a4f122cSGarrett D'Amore 		audio_dev_warn(dev->adev, "%s dma binding failed", desc);
13167a4f122cSGarrett D'Amore 		return (false);
13177a4f122cSGarrett D'Amore 	}
13187a4f122cSGarrett D'Amore 	e->paddr = c.dmac_address;
13197a4f122cSGarrett D'Amore 
13207a4f122cSGarrett D'Amore 	/*
13217a4f122cSGarrett D'Amore 	 * Allocate and configure audio engine.
13227a4f122cSGarrett D'Amore 	 */
13237a4f122cSGarrett D'Amore 	e->engine = audio_engine_alloc(&solo_engine_ops, caps);
13247a4f122cSGarrett D'Amore 	if (e->engine == NULL) {
13257a4f122cSGarrett D'Amore 		audio_dev_warn(dev->adev, "record audio_engine_alloc failed");
13267a4f122cSGarrett D'Amore 		return (false);
13277a4f122cSGarrett D'Amore 	}
13287a4f122cSGarrett D'Amore 
13297a4f122cSGarrett D'Amore 	audio_engine_set_private(e->engine, e);
13307a4f122cSGarrett D'Amore 	audio_dev_add_engine(dev->adev, e->engine);
13317a4f122cSGarrett D'Amore 
13327a4f122cSGarrett D'Amore 	return (true);
13337a4f122cSGarrett D'Amore }
13347a4f122cSGarrett D'Amore 
13357a4f122cSGarrett D'Amore 
13367a4f122cSGarrett D'Amore static int
solo_suspend(solo_dev_t * dev)13377a4f122cSGarrett D'Amore solo_suspend(solo_dev_t *dev)
13387a4f122cSGarrett D'Amore {
1339*68c47f65SGarrett D'Amore 	audio_dev_suspend(dev->adev);
13407a4f122cSGarrett D'Amore 
1341*68c47f65SGarrett D'Amore 	mutex_enter(&dev->mutex);
13427a4f122cSGarrett D'Amore 	dev->suspended = true;
13437a4f122cSGarrett D'Amore 	mutex_exit(&dev->mutex);
13447a4f122cSGarrett D'Amore 
13457a4f122cSGarrett D'Amore 	return (DDI_SUCCESS);
13467a4f122cSGarrett D'Amore }
13477a4f122cSGarrett D'Amore 
13487a4f122cSGarrett D'Amore static int
solo_resume(solo_dev_t * dev)13497a4f122cSGarrett D'Amore solo_resume(solo_dev_t *dev)
13507a4f122cSGarrett D'Amore {
13517a4f122cSGarrett D'Amore 	mutex_enter(&dev->mutex);
13527a4f122cSGarrett D'Amore 	if (!solo_init_hw(dev)) {
13537a4f122cSGarrett D'Amore 		/* yikes! */
13547a4f122cSGarrett D'Amore 		audio_dev_warn(dev->adev, "unable to resume audio!");
13557a4f122cSGarrett D'Amore 		audio_dev_warn(dev->adev, "reboot or reload driver to reset");
13567a4f122cSGarrett D'Amore 	}
13577a4f122cSGarrett D'Amore 	dev->suspended = false;
13587a4f122cSGarrett D'Amore 	mutex_exit(&dev->mutex);
13597a4f122cSGarrett D'Amore 
1360*68c47f65SGarrett D'Amore 	audio_dev_resume(dev->adev);
13617a4f122cSGarrett D'Amore 
13627a4f122cSGarrett D'Amore 	return (DDI_SUCCESS);
13637a4f122cSGarrett D'Amore }
13647a4f122cSGarrett D'Amore 
13657a4f122cSGarrett D'Amore static int
solo_attach(dev_info_t * dip)13667a4f122cSGarrett D'Amore solo_attach(dev_info_t *dip)
13677a4f122cSGarrett D'Amore {
13687a4f122cSGarrett D'Amore 	solo_dev_t	*dev;
13697a4f122cSGarrett D'Amore 	uint32_t	data;
13707a4f122cSGarrett D'Amore 
13717a4f122cSGarrett D'Amore 	dev = kmem_zalloc(sizeof (*dev), KM_SLEEP);
13727a4f122cSGarrett D'Amore 	dev->dip = dip;
13737a4f122cSGarrett D'Amore 	ddi_set_driver_private(dip, dev);
13747a4f122cSGarrett D'Amore 
13757a4f122cSGarrett D'Amore 	dev->adev = audio_dev_alloc(dip, 0);
13767a4f122cSGarrett D'Amore 	if (dev->adev == NULL)
13777a4f122cSGarrett D'Amore 		goto no;
13787a4f122cSGarrett D'Amore 
13797a4f122cSGarrett D'Amore 	audio_dev_set_description(dev->adev, "ESS Solo-1 PCI AudioDrive");
13807a4f122cSGarrett D'Amore 	audio_dev_set_version(dev->adev, "ES1938");
13817a4f122cSGarrett D'Amore 
13827a4f122cSGarrett D'Amore 	if (pci_config_setup(dip, &dev->pcih) != DDI_SUCCESS) {
13837a4f122cSGarrett D'Amore 		audio_dev_warn(NULL, "pci_config_setup failed");
13847a4f122cSGarrett D'Amore 		goto no;
13857a4f122cSGarrett D'Amore 	}
13867a4f122cSGarrett D'Amore 
13877a4f122cSGarrett D'Amore 	data = pci_config_get16(dev->pcih, PCI_CONF_COMM);
13887a4f122cSGarrett D'Amore 	data |= PCI_COMM_ME | PCI_COMM_IO;
13897a4f122cSGarrett D'Amore 	pci_config_put16(dev->pcih, PCI_CONF_COMM, data);
13907a4f122cSGarrett D'Amore 
13917a4f122cSGarrett D'Amore 	if ((!solo_map_registers(dev)) ||
13927a4f122cSGarrett D'Amore 	    (!solo_setup_interrupts(dev)) ||
13937a4f122cSGarrett D'Amore 	    (!solo_alloc_engine(dev, 1)) ||
13947a4f122cSGarrett D'Amore 	    (!solo_alloc_engine(dev, 2)) ||
13957a4f122cSGarrett D'Amore 	    (!solo_add_controls(dev)) ||
13967a4f122cSGarrett D'Amore 	    (!solo_init_hw(dev))) {
13977a4f122cSGarrett D'Amore 		goto no;
13987a4f122cSGarrett D'Amore 	}
13997a4f122cSGarrett D'Amore 
14007a4f122cSGarrett D'Amore 	if (audio_dev_register(dev->adev) != DDI_SUCCESS) {
14017a4f122cSGarrett D'Amore 		audio_dev_warn(dev->adev,
14027a4f122cSGarrett D'Amore 		    "unable to register with audio framework");
14037a4f122cSGarrett D'Amore 		goto no;
14047a4f122cSGarrett D'Amore 	}
14057a4f122cSGarrett D'Amore 
14067a4f122cSGarrett D'Amore 	(void) ddi_intr_enable(dev->ihandle);
14077a4f122cSGarrett D'Amore 	ddi_report_dev(dip);
14087a4f122cSGarrett D'Amore 
14097a4f122cSGarrett D'Amore 	return (DDI_SUCCESS);
14107a4f122cSGarrett D'Amore 
14117a4f122cSGarrett D'Amore no:
14127a4f122cSGarrett D'Amore 	solo_release_resources(dev);
14137a4f122cSGarrett D'Amore 	return (DDI_FAILURE);
14147a4f122cSGarrett D'Amore }
14157a4f122cSGarrett D'Amore 
14167a4f122cSGarrett D'Amore static int
solo_detach(solo_dev_t * dev)14177a4f122cSGarrett D'Amore solo_detach(solo_dev_t *dev)
14187a4f122cSGarrett D'Amore {
14197a4f122cSGarrett D'Amore 	if (audio_dev_unregister(dev->adev) != DDI_SUCCESS) {
14207a4f122cSGarrett D'Amore 		return (DDI_FAILURE);
14217a4f122cSGarrett D'Amore 	}
14227a4f122cSGarrett D'Amore 
14237a4f122cSGarrett D'Amore 	solo_release_resources(dev);
14247a4f122cSGarrett D'Amore 	return (DDI_SUCCESS);
14257a4f122cSGarrett D'Amore }
14267a4f122cSGarrett D'Amore 
14277a4f122cSGarrett D'Amore static int
solo_ddi_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)14287a4f122cSGarrett D'Amore solo_ddi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
14297a4f122cSGarrett D'Amore {
14307a4f122cSGarrett D'Amore 	solo_dev_t *dev;
14317a4f122cSGarrett D'Amore 
14327a4f122cSGarrett D'Amore 	switch (cmd) {
14337a4f122cSGarrett D'Amore 	case DDI_ATTACH:
14347a4f122cSGarrett D'Amore 		return (solo_attach(dip));
14357a4f122cSGarrett D'Amore 
14367a4f122cSGarrett D'Amore 	case DDI_RESUME:
14377a4f122cSGarrett D'Amore 		if ((dev = ddi_get_driver_private(dip)) == NULL) {
14387a4f122cSGarrett D'Amore 			return (DDI_FAILURE);
14397a4f122cSGarrett D'Amore 		}
14407a4f122cSGarrett D'Amore 		return (solo_resume(dev));
14417a4f122cSGarrett D'Amore 
14427a4f122cSGarrett D'Amore 	default:
14437a4f122cSGarrett D'Amore 		return (DDI_FAILURE);
14447a4f122cSGarrett D'Amore 	}
14457a4f122cSGarrett D'Amore }
14467a4f122cSGarrett D'Amore 
14477a4f122cSGarrett D'Amore static int
solo_ddi_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)14487a4f122cSGarrett D'Amore solo_ddi_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
14497a4f122cSGarrett D'Amore {
14507a4f122cSGarrett D'Amore 	solo_dev_t *dev;
14517a4f122cSGarrett D'Amore 
14527a4f122cSGarrett D'Amore 	if ((dev = ddi_get_driver_private(dip)) == NULL) {
14537a4f122cSGarrett D'Amore 		return (DDI_FAILURE);
14547a4f122cSGarrett D'Amore 	}
14557a4f122cSGarrett D'Amore 
14567a4f122cSGarrett D'Amore 	switch (cmd) {
14577a4f122cSGarrett D'Amore 	case DDI_DETACH:
14587a4f122cSGarrett D'Amore 		return (solo_detach(dev));
14597a4f122cSGarrett D'Amore 
14607a4f122cSGarrett D'Amore 	case DDI_SUSPEND:
14617a4f122cSGarrett D'Amore 		return (solo_suspend(dev));
14627a4f122cSGarrett D'Amore 	default:
14637a4f122cSGarrett D'Amore 		return (DDI_FAILURE);
14647a4f122cSGarrett D'Amore 	}
14657a4f122cSGarrett D'Amore }
14667a4f122cSGarrett D'Amore 
14677a4f122cSGarrett D'Amore static int
solo_quiesce(dev_info_t * dip)14687a4f122cSGarrett D'Amore solo_quiesce(dev_info_t *dip)
14697a4f122cSGarrett D'Amore {
14707a4f122cSGarrett D'Amore 	solo_dev_t *dev;
14717a4f122cSGarrett D'Amore 
14727a4f122cSGarrett D'Amore 	dev = ddi_get_driver_private(dip);
14737a4f122cSGarrett D'Amore 
14747a4f122cSGarrett D'Amore 	solo_aud1_stop(&dev->rec);
14757a4f122cSGarrett D'Amore 	solo_aud2_stop(&dev->play);
14767a4f122cSGarrett D'Amore 
14777a4f122cSGarrett D'Amore 	solo_setmixer(dev, 0, 0);
14787a4f122cSGarrett D'Amore 	PORT_WR8(dev->io, 0x7, 0); /* disable all irqs */
14797a4f122cSGarrett D'Amore 	return (0);
14807a4f122cSGarrett D'Amore }
14817a4f122cSGarrett D'Amore 
14827a4f122cSGarrett D'Amore struct dev_ops solo_dev_ops = {
14837a4f122cSGarrett D'Amore 	DEVO_REV,		/* rev */
14847a4f122cSGarrett D'Amore 	0,			/* refcnt */
14857a4f122cSGarrett D'Amore 	NULL,			/* getinfo */
14867a4f122cSGarrett D'Amore 	nulldev,		/* identify */
14877a4f122cSGarrett D'Amore 	nulldev,		/* probe */
14887a4f122cSGarrett D'Amore 	solo_ddi_attach,	/* attach */
14897a4f122cSGarrett D'Amore 	solo_ddi_detach,	/* detach */
14907a4f122cSGarrett D'Amore 	nodev,			/* reset */
14917a4f122cSGarrett D'Amore 	NULL,			/* cb_ops */
14927a4f122cSGarrett D'Amore 	NULL,			/* bus_ops */
14937a4f122cSGarrett D'Amore 	NULL,			/* power */
14947a4f122cSGarrett D'Amore 	solo_quiesce,		/* quiesce */
14957a4f122cSGarrett D'Amore };
14967a4f122cSGarrett D'Amore 
14977a4f122cSGarrett D'Amore static struct modldrv solo_modldrv = {
14987a4f122cSGarrett D'Amore 	&mod_driverops,			/* drv_modops */
14997a4f122cSGarrett D'Amore 	"ESS Solo-1 Audio",		/* linkinfo */
15007a4f122cSGarrett D'Amore 	&solo_dev_ops,			/* dev_ops */
15017a4f122cSGarrett D'Amore };
15027a4f122cSGarrett D'Amore 
15037a4f122cSGarrett D'Amore static struct modlinkage modlinkage = {
15047a4f122cSGarrett D'Amore 	MODREV_1,
15057a4f122cSGarrett D'Amore 	{ &solo_modldrv, NULL }
15067a4f122cSGarrett D'Amore };
15077a4f122cSGarrett D'Amore 
15087a4f122cSGarrett D'Amore int
_init(void)15097a4f122cSGarrett D'Amore _init(void)
15107a4f122cSGarrett D'Amore {
15117a4f122cSGarrett D'Amore 	int	rv;
15127a4f122cSGarrett D'Amore 
15137a4f122cSGarrett D'Amore 	audio_init_ops(&solo_dev_ops, DRVNAME);
15147a4f122cSGarrett D'Amore 	if ((rv = mod_install(&modlinkage)) != 0) {
15157a4f122cSGarrett D'Amore 		audio_fini_ops(&solo_dev_ops);
15167a4f122cSGarrett D'Amore 	}
15177a4f122cSGarrett D'Amore 	return (rv);
15187a4f122cSGarrett D'Amore }
15197a4f122cSGarrett D'Amore 
15207a4f122cSGarrett D'Amore int
_fini(void)15217a4f122cSGarrett D'Amore _fini(void)
15227a4f122cSGarrett D'Amore {
15237a4f122cSGarrett D'Amore 	int	rv;
15247a4f122cSGarrett D'Amore 
15257a4f122cSGarrett D'Amore 	if ((rv = mod_remove(&modlinkage)) == 0) {
15267a4f122cSGarrett D'Amore 		audio_fini_ops(&solo_dev_ops);
15277a4f122cSGarrett D'Amore 	}
15287a4f122cSGarrett D'Amore 	return (rv);
15297a4f122cSGarrett D'Amore }
15307a4f122cSGarrett D'Amore 
15317a4f122cSGarrett D'Amore int
_info(struct modinfo * modinfop)15327a4f122cSGarrett D'Amore _info(struct modinfo *modinfop)
15337a4f122cSGarrett D'Amore {
15347a4f122cSGarrett D'Amore 	return (mod_info(&modlinkage, modinfop));
15357a4f122cSGarrett D'Amore }
1536