xref: /netbsd-src/sys/arch/macppc/dev/awacs.c (revision bef84f1008fce2761e09cd1247eea5e8fdc0d378)
1*bef84f10Smacallan /*	$NetBSD: awacs.c,v 1.52 2023/08/30 08:38:51 macallan Exp $	*/
2308d7eb3Stsubai 
3308d7eb3Stsubai /*-
4308d7eb3Stsubai  * Copyright (c) 2000 Tsubai Masanari.  All rights reserved.
5308d7eb3Stsubai  *
6308d7eb3Stsubai  * Redistribution and use in source and binary forms, with or without
7308d7eb3Stsubai  * modification, are permitted provided that the following conditions
8308d7eb3Stsubai  * are met:
9308d7eb3Stsubai  * 1. Redistributions of source code must retain the above copyright
10308d7eb3Stsubai  *    notice, this list of conditions and the following disclaimer.
11308d7eb3Stsubai  * 2. Redistributions in binary form must reproduce the above copyright
12308d7eb3Stsubai  *    notice, this list of conditions and the following disclaimer in the
13308d7eb3Stsubai  *    documentation and/or other materials provided with the distribution.
14308d7eb3Stsubai  * 3. The name of the author may not be used to endorse or promote products
15308d7eb3Stsubai  *    derived from this software without specific prior written permission.
16308d7eb3Stsubai  *
17308d7eb3Stsubai  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18308d7eb3Stsubai  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19308d7eb3Stsubai  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20308d7eb3Stsubai  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21308d7eb3Stsubai  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22308d7eb3Stsubai  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23308d7eb3Stsubai  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24308d7eb3Stsubai  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25308d7eb3Stsubai  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26308d7eb3Stsubai  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27308d7eb3Stsubai  */
28308d7eb3Stsubai 
294b2744bfSlukem #include <sys/cdefs.h>
30*bef84f10Smacallan __KERNEL_RCSID(0, "$NetBSD: awacs.c,v 1.52 2023/08/30 08:38:51 macallan Exp $");
314b2744bfSlukem 
32308d7eb3Stsubai #include <sys/param.h>
33308d7eb3Stsubai #include <sys/audioio.h>
34308d7eb3Stsubai #include <sys/device.h>
35308d7eb3Stsubai #include <sys/systm.h>
363fff0522Smacallan #include <sys/kthread.h>
373fff0522Smacallan #include <sys/kernel.h>
388a962f23Sjmcneill #include <sys/mutex.h>
398a962f23Sjmcneill #include <sys/condvar.h>
40308d7eb3Stsubai 
41e622eac4Sisaki #include <dev/audio/audio_if.h>
42308d7eb3Stsubai 
43fbcd8328Smacallan #include <uvm/uvm_extern.h>
44308d7eb3Stsubai #include <machine/autoconf.h>
45308d7eb3Stsubai #include <machine/pio.h>
46d2f0c68dSmatt 
47d2f0c68dSmatt #include <dev/ofw/openfirm.h>
48308d7eb3Stsubai #include <macppc/dev/dbdma.h>
49308d7eb3Stsubai 
500990226cSmacallan #include <dev/i2c/sgsmixvar.h>
510990226cSmacallan #include "sgsmix.h"
523fff0522Smacallan #include "opt_awacs.h"
530990226cSmacallan 
54308d7eb3Stsubai #ifdef AWACS_DEBUG
55308d7eb3Stsubai # define DPRINTF printf
56308d7eb3Stsubai #else
57308d7eb3Stsubai # define DPRINTF while (0) printf
58308d7eb3Stsubai #endif
59308d7eb3Stsubai 
60308d7eb3Stsubai struct awacs_softc {
61b6c6870cSmacallan 	device_t sc_dev;
62e3b706f1Smacallan 	bus_space_tag_t sc_tag;
63e3b706f1Smacallan 	bus_space_handle_t	sc_regh;
64e3b706f1Smacallan 	bus_space_handle_t	sc_idmah;
65e3b706f1Smacallan 	bus_space_handle_t	sc_odmah;
661ffa7b76Swiz 	void (*sc_ointr)(void *);	/* DMA completion intr handler */
67308d7eb3Stsubai 	void *sc_oarg;			/* arg for sc_ointr() */
68308d7eb3Stsubai 	int sc_opages;			/* # of output pages */
69308d7eb3Stsubai 
701ffa7b76Swiz 	void (*sc_iintr)(void *);	/* DMA completion intr handler */
71308d7eb3Stsubai 	void *sc_iarg;			/* arg for sc_iintr() */
72308d7eb3Stsubai 
733fff0522Smacallan 	uint32_t sc_record_source;	/* recording source mask */
743fff0522Smacallan 	uint32_t sc_output_mask;	/* output mask */
753fff0522Smacallan 	uint32_t sc_headphones_mask;	/* which reading of the gpio means */
763fff0522Smacallan 	uint32_t sc_headphones_in;	/* headphones are present */
77308d7eb3Stsubai 
780990226cSmacallan 	int sc_screamer;
790990226cSmacallan 	int sc_have_perch;
803fff0522Smacallan 	int vol_l, vol_r;
810990226cSmacallan 	int sc_bass, sc_treble;
8288ab7da9Sad 	lwp_t *sc_thread;
838a962f23Sjmcneill 	kcondvar_t sc_event;
843fff0522Smacallan 	int sc_output_wanted;
855297b0caSjmcneill 	int sc_need_parallel_output;
860990226cSmacallan #if NSGSMIX > 0
87b6c6870cSmacallan 	device_t sc_sgsmix;
880990226cSmacallan #endif
890990226cSmacallan 
90308d7eb3Stsubai 	u_int sc_codecctl0;
91308d7eb3Stsubai 	u_int sc_codecctl1;
92308d7eb3Stsubai 	u_int sc_codecctl2;
93308d7eb3Stsubai 	u_int sc_codecctl4;
940990226cSmacallan 	u_int sc_codecctl5;
950990226cSmacallan 	u_int sc_codecctl6;
960990226cSmacallan 	u_int sc_codecctl7;
97308d7eb3Stsubai 	u_int sc_soundctl;
98308d7eb3Stsubai 
99308d7eb3Stsubai 	struct dbdma_regmap *sc_odma;
100308d7eb3Stsubai 	struct dbdma_regmap *sc_idma;
101308d7eb3Stsubai 	struct dbdma_command *sc_odmacmd;
102308d7eb3Stsubai 	struct dbdma_command *sc_idmacmd;
10323b5d914Skent 
1048a962f23Sjmcneill 	kmutex_t sc_lock;
105*bef84f10Smacallan 	kmutex_t sc_event_lock;
1068a962f23Sjmcneill 	kmutex_t sc_intr_lock;
107308d7eb3Stsubai };
108308d7eb3Stsubai 
109b6c6870cSmacallan static int awacs_match(device_t, struct cfdata *, void *);
110b6c6870cSmacallan static void awacs_attach(device_t, device_t, void *);
1113fff0522Smacallan static int awacs_intr(void *);
1123fff0522Smacallan static int awacs_status_intr(void *);
113308d7eb3Stsubai 
114e622eac4Sisaki static int awacs_query_format(void *, audio_format_query_t *);
115e622eac4Sisaki static int awacs_set_format(void *, int,
116e622eac4Sisaki 		     const audio_params_t *, const audio_params_t *,
117e622eac4Sisaki 		     audio_filter_reg_t *, audio_filter_reg_t *);
1183fff0522Smacallan 
1193fff0522Smacallan static int awacs_round_blocksize(void *, int, int, const audio_params_t *);
1203fff0522Smacallan static int awacs_trigger_output(void *, void *, void *, int, void (*)(void *),
12123b5d914Skent 			 void *, const audio_params_t *);
1223fff0522Smacallan static int awacs_trigger_input(void *, void *, void *, int, void (*)(void *),
12323b5d914Skent 			void *, const audio_params_t *);
1243fff0522Smacallan static int awacs_halt_output(void *);
1253fff0522Smacallan static int awacs_halt_input(void *);
1263fff0522Smacallan static int awacs_getdev(void *, struct audio_device *);
1273fff0522Smacallan static int awacs_set_port(void *, mixer_ctrl_t *);
1283fff0522Smacallan static int awacs_get_port(void *, mixer_ctrl_t *);
1293fff0522Smacallan static int awacs_query_devinfo(void *, mixer_devinfo_t *);
1303fff0522Smacallan static size_t awacs_round_buffersize(void *, int, size_t);
1313fff0522Smacallan static int awacs_get_props(void *);
1328a962f23Sjmcneill static void awacs_get_locks(void *, kmutex_t **, kmutex_t **);
133308d7eb3Stsubai 
134308d7eb3Stsubai static inline u_int awacs_read_reg(struct awacs_softc *, int);
135308d7eb3Stsubai static inline void awacs_write_reg(struct awacs_softc *, int, int);
1363fff0522Smacallan static void awacs_write_codec(struct awacs_softc *, int);
1373fff0522Smacallan 
138b6c6870cSmacallan void awacs_set_volume(struct awacs_softc *, int, int);
1393fff0522Smacallan static void awacs_set_speaker_volume(struct awacs_softc *, int, int);
1403fff0522Smacallan static void awacs_set_ext_volume(struct awacs_softc *, int, int);
1413fff0522Smacallan static void awacs_set_loopthrough_volume(struct awacs_softc *, int, int);
1423fff0522Smacallan static int awacs_set_rate(struct awacs_softc *, const audio_params_t *);
1433fff0522Smacallan static void awacs_select_output(struct awacs_softc *, int);
1443fff0522Smacallan static int awacs_check_headphones(struct awacs_softc *);
1453fff0522Smacallan static void awacs_thread(void *);
1463fff0522Smacallan 
1470990226cSmacallan #if NSGSMIX > 0
1480990226cSmacallan static void awacs_set_bass(struct awacs_softc *, int);
1490990226cSmacallan static void awacs_set_treble(struct awacs_softc *, int);
1500990226cSmacallan #endif
151b6c6870cSmacallan static int awacs_setup_sgsmix(device_t);
152308d7eb3Stsubai 
153b6c6870cSmacallan CFATTACH_DECL_NEW(awacs, sizeof(struct awacs_softc),
154c5e91d44Sthorpej     awacs_match, awacs_attach, NULL, NULL);
155308d7eb3Stsubai 
15618f717bbSyamt const struct audio_hw_if awacs_hw_if = {
157e622eac4Sisaki 	.query_format		= awacs_query_format,
158e622eac4Sisaki 	.set_format		= awacs_set_format,
1596291b134Sisaki 	.round_blocksize	= awacs_round_blocksize,
1606291b134Sisaki 	.halt_output		= awacs_halt_output,
1616291b134Sisaki 	.halt_input		= awacs_halt_input,
1626291b134Sisaki 	.getdev			= awacs_getdev,
1636291b134Sisaki 	.set_port		= awacs_set_port,
1646291b134Sisaki 	.get_port		= awacs_get_port,
1656291b134Sisaki 	.query_devinfo		= awacs_query_devinfo,
1666291b134Sisaki 	.round_buffersize	= awacs_round_buffersize,
1676291b134Sisaki 	.get_props		= awacs_get_props,
1686291b134Sisaki 	.trigger_output		= awacs_trigger_output,
1696291b134Sisaki 	.trigger_input		= awacs_trigger_input,
1706291b134Sisaki 	.get_locks		= awacs_get_locks,
171308d7eb3Stsubai };
172308d7eb3Stsubai 
173308d7eb3Stsubai struct audio_device awacs_device = {
174308d7eb3Stsubai 	"AWACS",
175308d7eb3Stsubai 	"",
176308d7eb3Stsubai 	"awacs"
177308d7eb3Stsubai };
178308d7eb3Stsubai 
179e622eac4Sisaki static const struct audio_format awacs_formats[] = {
180e622eac4Sisaki 	{
181e622eac4Sisaki 		.mode		= AUMODE_PLAY | AUMODE_RECORD,
182e622eac4Sisaki 		.encoding	= AUDIO_ENCODING_SLINEAR_BE,
183e622eac4Sisaki 		.validbits	= 16,
184e622eac4Sisaki 		.precision	= 16,
185e622eac4Sisaki 		.channels	= 2,
186e622eac4Sisaki 		.channel_mask	= AUFMT_STEREO,
187e622eac4Sisaki 		.frequency_type	= 8,
188e622eac4Sisaki 		.frequency	=
189e622eac4Sisaki 		    { 7350, 8820, 11025, 14700, 17640, 22050, 29400, 44100 },
190e622eac4Sisaki 	}
19123b5d914Skent };
192e622eac4Sisaki #define AWACS_NFORMATS		__arraycount(awacs_formats)
19323b5d914Skent 
194308d7eb3Stsubai /* register offset */
195308d7eb3Stsubai #define AWACS_SOUND_CTRL	0x00
196308d7eb3Stsubai #define AWACS_CODEC_CTRL	0x10
197308d7eb3Stsubai #define AWACS_CODEC_STATUS	0x20
198308d7eb3Stsubai #define AWACS_CLIP_COUNT	0x30
199308d7eb3Stsubai #define AWACS_BYTE_SWAP		0x40
200308d7eb3Stsubai 
201308d7eb3Stsubai /* sound control */
202308d7eb3Stsubai #define AWACS_INPUT_SUBFRAME0	0x00000001
203308d7eb3Stsubai #define AWACS_INPUT_SUBFRAME1	0x00000002
204308d7eb3Stsubai #define AWACS_INPUT_SUBFRAME2	0x00000004
205308d7eb3Stsubai #define AWACS_INPUT_SUBFRAME3	0x00000008
206308d7eb3Stsubai 
207308d7eb3Stsubai #define AWACS_OUTPUT_SUBFRAME0	0x00000010
208308d7eb3Stsubai #define AWACS_OUTPUT_SUBFRAME1	0x00000020
209308d7eb3Stsubai #define AWACS_OUTPUT_SUBFRAME2	0x00000040
210308d7eb3Stsubai #define AWACS_OUTPUT_SUBFRAME3	0x00000080
211308d7eb3Stsubai 
212308d7eb3Stsubai #define AWACS_RATE_44100	0x00000000
213308d7eb3Stsubai #define AWACS_RATE_29400	0x00000100
214308d7eb3Stsubai #define AWACS_RATE_22050	0x00000200
215308d7eb3Stsubai #define AWACS_RATE_17640	0x00000300
216308d7eb3Stsubai #define AWACS_RATE_14700	0x00000400
217308d7eb3Stsubai #define AWACS_RATE_11025	0x00000500
218308d7eb3Stsubai #define AWACS_RATE_8820		0x00000600
219308d7eb3Stsubai #define AWACS_RATE_7350		0x00000700
220308d7eb3Stsubai #define AWACS_RATE_MASK		0x00000700
221308d7eb3Stsubai 
2223fff0522Smacallan #define AWACS_ERROR		0x00000800
2233fff0522Smacallan #define AWACS_PORTCHG		0x00001000
2243fff0522Smacallan #define AWACS_INTR_ERROR	0x00002000	/* interrupt on error */
2253fff0522Smacallan #define AWACS_INTR_PORTCHG	0x00004000	/* interrupt on port change */
2263fff0522Smacallan 
2273fff0522Smacallan #define AWACS_STATUS_SUBFRAME	0x00018000	/* mask */
2286a9cc7b7Sjmcneill 
229308d7eb3Stsubai /* codec control */
230308d7eb3Stsubai #define AWACS_CODEC_ADDR0	0x00000000
231308d7eb3Stsubai #define AWACS_CODEC_ADDR1	0x00001000
232308d7eb3Stsubai #define AWACS_CODEC_ADDR2	0x00002000
233308d7eb3Stsubai #define AWACS_CODEC_ADDR4	0x00004000
2340990226cSmacallan #define AWACS_CODEC_ADDR5	0x00005000
2350990226cSmacallan #define AWACS_CODEC_ADDR6	0x00006000
2360990226cSmacallan #define AWACS_CODEC_ADDR7	0x00007000
237308d7eb3Stsubai #define AWACS_CODEC_EMSEL0	0x00000000
238308d7eb3Stsubai #define AWACS_CODEC_EMSEL1	0x00400000
239308d7eb3Stsubai #define AWACS_CODEC_EMSEL2	0x00800000
240308d7eb3Stsubai #define AWACS_CODEC_EMSEL4	0x00c00000
241308d7eb3Stsubai #define AWACS_CODEC_BUSY	0x01000000
242308d7eb3Stsubai 
243308d7eb3Stsubai /* cc0 */
244308d7eb3Stsubai #define AWACS_DEFAULT_CD_GAIN	0x000000bb
245308d7eb3Stsubai #define AWACS_INPUT_CD		0x00000200
2460a4ef18aSwiz #define AWACS_INPUT_LINE	0x00000400
2470a4ef18aSwiz #define AWACS_INPUT_MICROPHONE	0x00000800
248308d7eb3Stsubai #define AWACS_INPUT_MASK	0x00000e00
249308d7eb3Stsubai 
250308d7eb3Stsubai /* cc1 */
2510990226cSmacallan #define AWACS_LOOP_THROUGH	0x00000040
252308d7eb3Stsubai #define AWACS_MUTE_SPEAKER	0x00000080
253308d7eb3Stsubai #define AWACS_MUTE_HEADPHONE	0x00000200
2540990226cSmacallan #define AWACS_PARALLEL_OUTPUT	0x00000c00
255308d7eb3Stsubai 
2560990226cSmacallan /* output */
2570990226cSmacallan #define OUTPUT_SPEAKER		1
2580990226cSmacallan #define OUTPUT_HEADPHONES	2
2593fff0522Smacallan 
2603fff0522Smacallan /* codec status */
2613fff0522Smacallan 
2623fff0522Smacallan static const char *screamer[] = {"screamer", NULL};
2633fff0522Smacallan 
2643fff0522Smacallan /*
2653fff0522Smacallan  * list machines that have the headphone detect GPIO reversed here.
2663fff0522Smacallan  * so far the only known case is the PowerBook 3400c and similar machines
2673fff0522Smacallan  */
2683fff0522Smacallan static const char *detect_reversed[] = {"AAPL,3400/2400",
2693fff0522Smacallan 					"AAPL,3500",
2703fff0522Smacallan 					NULL};
2713fff0522Smacallan 
27210db3a4bSphx static const char *use_gpio4[] = {	"PowerMac3,1",
27310db3a4bSphx 					"PowerMac3,2",
27410db3a4bSphx 					"PowerMac3,3",
27571e51863Smacallan 					NULL};
27671e51863Smacallan 
2775297b0caSjmcneill /*
2785297b0caSjmcneill  * list of machines that do not require AWACS_PARALLEL_OUTPUT
2795297b0caSjmcneill  */
2805297b0caSjmcneill static const char *no_parallel_output[] = {	"PowerBook3,1",
2815297b0caSjmcneill 						NULL};
2825297b0caSjmcneill 
2833fff0522Smacallan static int
awacs_match(device_t parent,struct cfdata * match,void * aux)284b6c6870cSmacallan awacs_match(device_t parent, struct cfdata *match, void *aux)
285308d7eb3Stsubai {
28693293b9eSkent 	struct confargs *ca;
287308d7eb3Stsubai 
28893293b9eSkent 	ca = aux;
289889bccccSsoren 
29071e51863Smacallan 	if (strcmp(ca->ca_name, "awacs") == 0 ||
29171e51863Smacallan 	    strcmp(ca->ca_name, "davbus") == 0)
29271e51863Smacallan 		return 100;
293308d7eb3Stsubai 
294308d7eb3Stsubai 	if (ca->ca_nreg < 24 || ca->ca_nintr < 12)
295308d7eb3Stsubai 		return 0;
296308d7eb3Stsubai 
29771e51863Smacallan 	if (strcmp(ca->ca_name, "i2s") == 0)
298308d7eb3Stsubai 		return 1;
29971e51863Smacallan 
30071e51863Smacallan 	return 0;
301308d7eb3Stsubai }
302308d7eb3Stsubai 
3033fff0522Smacallan static void
awacs_attach(device_t parent,device_t self,void * aux)304b6c6870cSmacallan awacs_attach(device_t parent, device_t self, void *aux)
305308d7eb3Stsubai {
30693293b9eSkent 	struct awacs_softc *sc;
307e3b706f1Smacallan 	struct confargs *ca = aux;
308b672929bStsubai 	int cirq, oirq, iirq, cirq_type, oirq_type, iirq_type;
3090990226cSmacallan 	int len = -1, perch;
3103fff0522Smacallan 	int root_node;
311cfe2093dSrin 	char compat[256], intr_xname[INTRDEVNAMEBUF];
312308d7eb3Stsubai 
313b6c6870cSmacallan 	sc = device_private(self);
314b6c6870cSmacallan 	sc->sc_dev = self;
315e3b706f1Smacallan 	sc->sc_tag = ca->ca_tag;
316308d7eb3Stsubai 
317e3b706f1Smacallan 	if (bus_space_map(sc->sc_tag, ca->ca_baseaddr + ca->ca_reg[0],
318e3b706f1Smacallan 	    ca->ca_reg[1], 0, &sc->sc_regh) != 0)
319e3b706f1Smacallan 		printf("couldn't map codec registers\n");
320e3b706f1Smacallan 	if (bus_space_map(sc->sc_tag, ca->ca_baseaddr + ca->ca_reg[2],
321e3b706f1Smacallan 	    ca->ca_reg[3], BUS_SPACE_MAP_LINEAR, &sc->sc_odmah) != 0)
322e3b706f1Smacallan 		printf("couldn't map DMA out registers\n");
323e3b706f1Smacallan 	if (bus_space_map(sc->sc_tag, ca->ca_baseaddr + ca->ca_reg[4],
324e3b706f1Smacallan 	    ca->ca_reg[5], BUS_SPACE_MAP_LINEAR, &sc->sc_idmah) != 0)
325e3b706f1Smacallan 		printf("couldn't map DMA in registers\n");
326308d7eb3Stsubai 
327e3b706f1Smacallan 	sc->sc_odma = bus_space_vaddr(sc->sc_tag, sc->sc_odmah);
328e3b706f1Smacallan 	sc->sc_idma = bus_space_vaddr(sc->sc_tag, sc->sc_idmah);
329c9a4f92cSmacallan 	sc->sc_odmacmd = dbdma_alloc(20 * sizeof(struct dbdma_command), NULL);
330c9a4f92cSmacallan 	sc->sc_idmacmd = dbdma_alloc(20 * sizeof(struct dbdma_command), NULL);
331308d7eb3Stsubai 
332889bccccSsoren 	if (strcmp(ca->ca_name, "i2s") == 0) {
333889bccccSsoren 		int node, intr[6];
334889bccccSsoren 
335889bccccSsoren 		node = OF_child(ca->ca_node);
336dec320ebSthorpej 		if (node == 0) {
337889bccccSsoren 			printf("no i2s-a child\n");
338889bccccSsoren 			return;
339889bccccSsoren 		}
340889bccccSsoren 		if (OF_getprop(node, "interrupts", intr, sizeof(intr)) == -1) {
341889bccccSsoren 			printf("no interrupt property\n");
342889bccccSsoren 			return;
343889bccccSsoren 		}
344889bccccSsoren 
345889bccccSsoren 		cirq = intr[0];
346889bccccSsoren 		oirq = intr[2];
347889bccccSsoren 		iirq = intr[4];
348889bccccSsoren 		cirq_type = intr[1] ? IST_LEVEL : IST_EDGE;
349889bccccSsoren 		oirq_type = intr[3] ? IST_LEVEL : IST_EDGE;
350889bccccSsoren 		iirq_type = intr[5] ? IST_LEVEL : IST_EDGE;
351889bccccSsoren 	} else if (ca->ca_nintr == 24) {
352b672929bStsubai 		cirq = ca->ca_intr[0];
353b672929bStsubai 		oirq = ca->ca_intr[2];
354b672929bStsubai 		iirq = ca->ca_intr[4];
355b672929bStsubai 		cirq_type = ca->ca_intr[1] ? IST_LEVEL : IST_EDGE;
356b672929bStsubai 		oirq_type = ca->ca_intr[3] ? IST_LEVEL : IST_EDGE;
357b672929bStsubai 		iirq_type = ca->ca_intr[5] ? IST_LEVEL : IST_EDGE;
358b672929bStsubai 	} else {
359b672929bStsubai 		cirq = ca->ca_intr[0];
360b672929bStsubai 		oirq = ca->ca_intr[1];
361b672929bStsubai 		iirq = ca->ca_intr[2];
362d974db0aSgarbled 		cirq_type = oirq_type = iirq_type = IST_EDGE;
363b672929bStsubai 	}
364889bccccSsoren 
365cfe2093dSrin 	snprintf(intr_xname, sizeof(intr_xname), "%s status",
366cfe2093dSrin 	    device_xname(self));
367cfe2093dSrin 	intr_establish_xname(cirq, cirq_type, IPL_BIO, awacs_status_intr, sc,
368cfe2093dSrin 	    intr_xname);
369cfe2093dSrin 
370cfe2093dSrin 	snprintf(intr_xname, sizeof(intr_xname), "%s out", device_xname(self));
371cfe2093dSrin 	intr_establish_xname(oirq, oirq_type, IPL_AUDIO, awacs_intr, sc,
372cfe2093dSrin 	    intr_xname);
373cfe2093dSrin 
374cfe2093dSrin 	snprintf(intr_xname, sizeof(intr_xname), "%s in", device_xname(self));
375cfe2093dSrin 	intr_establish_xname(iirq, iirq_type, IPL_AUDIO, awacs_intr, sc,
376cfe2093dSrin 	    intr_xname);
3778a962f23Sjmcneill 
3788a962f23Sjmcneill 	mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
379*bef84f10Smacallan 	mutex_init(&sc->sc_event_lock, MUTEX_DEFAULT, IPL_NONE);
3808de08fa9Smrg 	mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_AUDIO);
3818a962f23Sjmcneill 
3828a962f23Sjmcneill 	cv_init(&sc->sc_event, "awacs_wait");
3830990226cSmacallan 
3840990226cSmacallan 	/* check if the chip is a screamer */
38522c149c8Sthorpej 	sc->sc_screamer = of_compatible(ca->ca_node, screamer);
3863fff0522Smacallan 	if (!sc->sc_screamer) {
3873fff0522Smacallan 		/* look for 'sound' child node */
3883fff0522Smacallan 		int sound_node;
3890990226cSmacallan 
3903fff0522Smacallan 		sound_node = OF_child(ca->ca_node);
3913fff0522Smacallan 		while ((sound_node != 0) && (!sc->sc_screamer)) {
3920990226cSmacallan 
39322c149c8Sthorpej 			sc->sc_screamer = of_compatible(sound_node, screamer);
3943fff0522Smacallan 			sound_node = OF_peer(sound_node);
3950990226cSmacallan 		}
3960990226cSmacallan 	}
3973fff0522Smacallan 
3983fff0522Smacallan 	if (sc->sc_screamer) {
3990990226cSmacallan 		printf(" Screamer");
4003fff0522Smacallan 	}
4010990226cSmacallan 
4023fff0522Smacallan 	printf(": irq %d,%d,%d\n", cirq, oirq, iirq);
4033fff0522Smacallan 
4043fff0522Smacallan 	sc->vol_l = 0;
4053fff0522Smacallan 	sc->vol_r = 0;
4060990226cSmacallan 
407308d7eb3Stsubai 	sc->sc_soundctl = AWACS_INPUT_SUBFRAME0 | AWACS_OUTPUT_SUBFRAME0 |
4083fff0522Smacallan 		AWACS_RATE_44100 | AWACS_INTR_PORTCHG;
409308d7eb3Stsubai 	awacs_write_reg(sc, AWACS_SOUND_CTRL, sc->sc_soundctl);
410308d7eb3Stsubai 
411308d7eb3Stsubai 	sc->sc_codecctl0 = AWACS_CODEC_ADDR0 | AWACS_CODEC_EMSEL0;
412308d7eb3Stsubai 	sc->sc_codecctl1 = AWACS_CODEC_ADDR1 | AWACS_CODEC_EMSEL0;
413308d7eb3Stsubai 	sc->sc_codecctl2 = AWACS_CODEC_ADDR2 | AWACS_CODEC_EMSEL0;
414308d7eb3Stsubai 	sc->sc_codecctl4 = AWACS_CODEC_ADDR4 | AWACS_CODEC_EMSEL0;
4150990226cSmacallan 	sc->sc_codecctl5 = AWACS_CODEC_ADDR5 | AWACS_CODEC_EMSEL0;
4160990226cSmacallan 	sc->sc_codecctl6 = AWACS_CODEC_ADDR6 | AWACS_CODEC_EMSEL0;
4170990226cSmacallan 	sc->sc_codecctl7 = AWACS_CODEC_ADDR7 | AWACS_CODEC_EMSEL0;
418308d7eb3Stsubai 
419308d7eb3Stsubai 	sc->sc_codecctl0 |= AWACS_INPUT_CD | AWACS_DEFAULT_CD_GAIN;
420308d7eb3Stsubai 	awacs_write_codec(sc, sc->sc_codecctl0);
421308d7eb3Stsubai 
4223fff0522Smacallan 	/* Set loopthrough for external mixer on beige G3 */
4235297b0caSjmcneill 	sc->sc_codecctl1 |= AWACS_LOOP_THROUGH;
424308d7eb3Stsubai 
425b6c6870cSmacallan         printf("%s: ", device_xname(sc->sc_dev));
4263fff0522Smacallan 
427b5dc0007Smacallan 	/*
428b5dc0007Smacallan 	 * all(?) awacs have GPIOs to detect if there's something plugged into
429b5dc0007Smacallan 	 * the headphone jack. The other GPIOs are either used for other jacks
430b5dc0007Smacallan 	 * ( the PB3400c's microphone jack for instance ) or unused.
431b5dc0007Smacallan 	 * The problem is that there are at least three different ways how
432b5dc0007Smacallan 	 * those GPIOs are wired to the actual jacks.
433b5dc0007Smacallan 	 * For now we bother only with headphone detection
434b5dc0007Smacallan 	 */
435b5dc0007Smacallan 	perch = OF_finddevice("/perch");
4363fff0522Smacallan 	root_node = OF_finddevice("/");
43722c149c8Sthorpej 	if (of_compatible(root_node, detect_reversed)) {
4383fff0522Smacallan 
4393fff0522Smacallan 		/* 0x02 is for the microphone jack, high active */
4403fff0522Smacallan 		/*
4413fff0522Smacallan 		 * for some reason the gpio for the headphones jack is low
4423fff0522Smacallan 		 * active on the PB3400 and similar machines
4433fff0522Smacallan 		 */
4443fff0522Smacallan 		sc->sc_headphones_mask = 0x8;
4453fff0522Smacallan 		sc->sc_headphones_in = 0x0;
44622c149c8Sthorpej 	} else if ((perch != -1) || of_compatible(root_node, use_gpio4)) {
4473fff0522Smacallan 		/*
448b5dc0007Smacallan 		 * this is for the beige G3's 'personality card' which uses
449b5dc0007Smacallan 		 * yet another wiring of the headphone detect GPIOs
45071e51863Smacallan 		 * some G4s use it as well
4513fff0522Smacallan 		 */
4523fff0522Smacallan 		sc->sc_headphones_mask = 0x04;
4533fff0522Smacallan 		sc->sc_headphones_in = 0x04;
4540990226cSmacallan 	} else {
455b5dc0007Smacallan 		/* while on most machines it's high active as well */
4563fff0522Smacallan 		sc->sc_headphones_mask = 0x8;
4573fff0522Smacallan 		sc->sc_headphones_in = 0x8;
4580990226cSmacallan 	}
4595297b0caSjmcneill 
46022c149c8Sthorpej 	if (of_compatible(root_node, no_parallel_output))
4615297b0caSjmcneill 		sc->sc_need_parallel_output = 0;
4625297b0caSjmcneill 	else {
4635297b0caSjmcneill 		sc->sc_need_parallel_output = 1;
4645297b0caSjmcneill 		sc->sc_codecctl1 |= AWACS_PARALLEL_OUTPUT;
4655297b0caSjmcneill 	}
4665297b0caSjmcneill 
4673fff0522Smacallan 	if (awacs_check_headphones(sc)) {
4683fff0522Smacallan 
4693fff0522Smacallan                 /* default output to headphones */
4703fff0522Smacallan                 printf("headphones\n");
4713fff0522Smacallan                 sc->sc_output_mask = OUTPUT_HEADPHONES;
4723fff0522Smacallan         } else {
4733fff0522Smacallan 
4743fff0522Smacallan                 /* default output to speakers */
4753fff0522Smacallan                 printf("speaker\n");
4763fff0522Smacallan                 sc->sc_output_mask = OUTPUT_SPEAKER;
4773fff0522Smacallan         }
4783fff0522Smacallan 	sc->sc_output_wanted = sc->sc_output_mask;
4793fff0522Smacallan 	awacs_select_output(sc, sc->sc_output_mask);
480308d7eb3Stsubai 
4810990226cSmacallan 	delay(100);
4820990226cSmacallan 	if (sc->sc_screamer) {
4830990226cSmacallan 		awacs_write_codec(sc, sc->sc_codecctl6);
4840990226cSmacallan 		awacs_write_codec(sc, sc->sc_codecctl5);
4850990226cSmacallan 		delay(2);
4860990226cSmacallan 		awacs_write_codec(sc, sc->sc_codecctl1);
4870990226cSmacallan 		awacs_write_codec(sc, sc->sc_codecctl7);
4880990226cSmacallan 	}
4890990226cSmacallan 
4900a4ef18aSwiz 	/* default input from CD */
4910a4ef18aSwiz 	sc->sc_record_source = 1 << 0;
4920a4ef18aSwiz 	sc->sc_codecctl0 &= ~AWACS_INPUT_MASK;
4930a4ef18aSwiz 	sc->sc_codecctl0 |= AWACS_INPUT_CD;
4940a4ef18aSwiz 	awacs_write_codec(sc, sc->sc_codecctl0);
4950a4ef18aSwiz 
496308d7eb3Stsubai 	/* Enable interrupts and looping mode. */
497308d7eb3Stsubai 	/* XXX ... */
498308d7eb3Stsubai 
4995297b0caSjmcneill 	sc->sc_codecctl1 |= AWACS_LOOP_THROUGH;
5005297b0caSjmcneill 	if (sc->sc_need_parallel_output)
5015297b0caSjmcneill 		sc->sc_codecctl1 |= AWACS_PARALLEL_OUTPUT;
5020990226cSmacallan 	awacs_write_codec(sc, sc->sc_codecctl1);
5030990226cSmacallan 
5040990226cSmacallan #if NSGSMIX > 0
5050990226cSmacallan 	sc->sc_sgsmix = NULL;
5060990226cSmacallan #endif
5070990226cSmacallan 	sc->sc_have_perch = 0;
508b5dc0007Smacallan 	if (perch != -1) {
5090990226cSmacallan 
5100990226cSmacallan 		len = OF_getprop(perch, "compatible", compat, 255);
5110990226cSmacallan 		if (len > 0) {
5120990226cSmacallan 			printf("%s: found '%s' personality card\n",
513b6c6870cSmacallan 			    device_xname(sc->sc_dev), compat);
5140990226cSmacallan 			sc->sc_have_perch = 1;
515b6c6870cSmacallan 			config_finalize_register(sc->sc_dev,
5161f070ed9Smacallan 			    awacs_setup_sgsmix);
5170990226cSmacallan 		}
5180990226cSmacallan 	}
5190990226cSmacallan 
5200990226cSmacallan 	/* Set initial volume[s] */
5213fff0522Smacallan 	awacs_set_volume(sc, 144, 144);
5221f070ed9Smacallan 	awacs_set_loopthrough_volume(sc, 0, 0);
5230990226cSmacallan 
524b6c6870cSmacallan 	audio_attach_mi(&awacs_hw_if, sc, sc->sc_dev);
5253fff0522Smacallan 
52688ab7da9Sad 	if (kthread_create(PRI_NONE, 0, NULL, awacs_thread, sc,
52788ab7da9Sad 	    &sc->sc_thread, "%s", "awacs") != 0) {
52888ab7da9Sad 		printf("awacs: unable to create event kthread");
52988ab7da9Sad 	}
53048d31d1eSmacallan 	pmf_device_register(sc->sc_dev, NULL, NULL);
531308d7eb3Stsubai }
532308d7eb3Stsubai 
5330990226cSmacallan static int
awacs_setup_sgsmix(device_t cookie)534b6c6870cSmacallan awacs_setup_sgsmix(device_t cookie)
5350990226cSmacallan {
536b6c6870cSmacallan 	struct awacs_softc *sc = device_private(cookie);
5370990226cSmacallan #if NSGSMIX > 0
538b6c6870cSmacallan 	device_t dv;
5397507fa68Sdyoung 	deviter_t di;
5400990226cSmacallan #endif
5410990226cSmacallan 
5420990226cSmacallan 	if (!sc->sc_have_perch)
5430990226cSmacallan 		return 0;
5440990226cSmacallan #if NSGSMIX > 0
5450990226cSmacallan 	/* look for sgsmix */
5467507fa68Sdyoung 	for (dv = deviter_first(&di, DEVITER_F_ROOT_FIRST);
5477507fa68Sdyoung 	     dv != NULL;
5487507fa68Sdyoung 	     dv = deviter_next(&di)) {
5490990226cSmacallan 		if (device_is_a(dv, "sgsmix")) {
5500990226cSmacallan 			sc->sc_sgsmix = dv;
5510990226cSmacallan 			break;
5520990226cSmacallan 		}
5530990226cSmacallan 	}
5547507fa68Sdyoung 	deviter_release(&di);
5550990226cSmacallan 	if (sc->sc_sgsmix == NULL)
5560990226cSmacallan 		return 0;
5570990226cSmacallan 
558b6c6870cSmacallan 	printf("%s: using %s\n", device_xname(sc->sc_dev),
559b6c6870cSmacallan 	    device_xname(sc->sc_sgsmix));
5600990226cSmacallan 
561e3b706f1Smacallan 	sc->sc_codecctl1 &= ~AWACS_MUTE_HEADPHONE;
562e3b706f1Smacallan 	awacs_write_codec(sc, sc->sc_codecctl1);
563e3b706f1Smacallan 
5640990226cSmacallan 	awacs_select_output(sc, sc->sc_output_mask);
5653fff0522Smacallan 	awacs_set_volume(sc, sc->vol_l, sc->vol_r);
5660990226cSmacallan 	awacs_set_bass(sc, 128);
5670990226cSmacallan 	awacs_set_treble(sc, 128);
5688a962f23Sjmcneill 	cv_signal(&sc->sc_event);
5690990226cSmacallan #endif
5700990226cSmacallan 	return 0;
5710990226cSmacallan }
5720990226cSmacallan 
5730990226cSmacallan 
574d4bcd9c7Ssimonb static inline u_int
awacs_read_reg(struct awacs_softc * sc,int reg)57593293b9eSkent awacs_read_reg(struct awacs_softc *sc, int reg)
576308d7eb3Stsubai {
577308d7eb3Stsubai 
578e3b706f1Smacallan 	return bus_space_read_4(sc->sc_tag, sc->sc_regh, reg);
579308d7eb3Stsubai }
580308d7eb3Stsubai 
581d4bcd9c7Ssimonb static inline void
awacs_write_reg(struct awacs_softc * sc,int reg,int val)58293293b9eSkent awacs_write_reg(struct awacs_softc *sc, int reg, int val)
583308d7eb3Stsubai {
584308d7eb3Stsubai 
585e3b706f1Smacallan 	bus_space_write_4(sc->sc_tag, sc->sc_regh, reg, val);
586308d7eb3Stsubai }
587308d7eb3Stsubai 
5883fff0522Smacallan static void
awacs_write_codec(struct awacs_softc * sc,int value)58993293b9eSkent awacs_write_codec(struct awacs_softc *sc, int value)
590308d7eb3Stsubai {
59193293b9eSkent 
5920990226cSmacallan 	do {
5930990226cSmacallan 		delay(100);
5940990226cSmacallan 	} while (awacs_read_reg(sc, AWACS_CODEC_CTRL) & AWACS_CODEC_BUSY);
5950990226cSmacallan 
596308d7eb3Stsubai 	awacs_write_reg(sc, AWACS_CODEC_CTRL, value);
5970990226cSmacallan 
5980990226cSmacallan 	do {
5990990226cSmacallan 		delay(100);
6000990226cSmacallan 	} while (awacs_read_reg(sc, AWACS_CODEC_CTRL) & AWACS_CODEC_BUSY);
601308d7eb3Stsubai }
602308d7eb3Stsubai 
6033fff0522Smacallan static int
awacs_intr(void * v)60493293b9eSkent awacs_intr(void *v)
605308d7eb3Stsubai {
60693293b9eSkent 	struct awacs_softc *sc;
60793293b9eSkent 	struct dbdma_command *cmd;
60893293b9eSkent 	int count;
609308d7eb3Stsubai 	int status;
610308d7eb3Stsubai 
61193293b9eSkent 	sc = v;
6128a962f23Sjmcneill 	mutex_spin_enter(&sc->sc_intr_lock);
61393293b9eSkent 	cmd = sc->sc_odmacmd;
61493293b9eSkent 	count = sc->sc_opages;
615308d7eb3Stsubai 	/* Fill used buffer(s). */
616308d7eb3Stsubai 	while (count-- > 0) {
617308d7eb3Stsubai 		/* if DBDMA_INT_ALWAYS */
618308d7eb3Stsubai 		if (in16rb(&cmd->d_command) & 0x30) {	/* XXX */
619308d7eb3Stsubai 			status = in16rb(&cmd->d_status);
620308d7eb3Stsubai 			cmd->d_status = 0;
621308d7eb3Stsubai 			if (status)	/* status == 0x8400 */
622308d7eb3Stsubai 				if (sc->sc_ointr)
623308d7eb3Stsubai 					(*sc->sc_ointr)(sc->sc_oarg);
624308d7eb3Stsubai 		}
625308d7eb3Stsubai 		cmd++;
626308d7eb3Stsubai 	}
6278a962f23Sjmcneill 	mutex_spin_exit(&sc->sc_intr_lock);
628308d7eb3Stsubai 
629308d7eb3Stsubai 	return 1;
630308d7eb3Stsubai }
631308d7eb3Stsubai 
6323fff0522Smacallan static int
awacs_query_format(void * h,audio_format_query_t * afp)633e622eac4Sisaki awacs_query_format(void *h, audio_format_query_t *afp)
634308d7eb3Stsubai {
635b672929bStsubai 
636e622eac4Sisaki 	return audio_query_format(awacs_formats, AWACS_NFORMATS, afp);
637308d7eb3Stsubai }
638308d7eb3Stsubai 
6393fff0522Smacallan static int
awacs_set_format(void * h,int setmode,const audio_params_t * play,const audio_params_t * rec,audio_filter_reg_t * pfil,audio_filter_reg_t * rfil)640e622eac4Sisaki awacs_set_format(void *h, int setmode,
641e622eac4Sisaki 		 const audio_params_t *play, const audio_params_t *rec,
642e622eac4Sisaki 		 audio_filter_reg_t *pfil, audio_filter_reg_t *rfil)
643308d7eb3Stsubai {
64493293b9eSkent 	struct awacs_softc *sc;
645308d7eb3Stsubai 
64693293b9eSkent 	sc = h;
647308d7eb3Stsubai 
648e622eac4Sisaki 	/* *play and *rec are the identical because !AUDIO_PROP_INDEPENDENT. */
649308d7eb3Stsubai 
65023b5d914Skent 	awacs_write_reg(sc, AWACS_BYTE_SWAP, 0);
651e622eac4Sisaki 
652e622eac4Sisaki 	if (awacs_set_rate(sc, play))
6534382a59dStsubai 		return EINVAL;
654308d7eb3Stsubai 	return 0;
655308d7eb3Stsubai }
656308d7eb3Stsubai 
6573fff0522Smacallan static int
awacs_round_blocksize(void * h,int size,int mode,const audio_params_t * param)65893293b9eSkent awacs_round_blocksize(void *h, int size, int mode, const audio_params_t *param)
659308d7eb3Stsubai {
66093293b9eSkent 
661d470cda5Sthorpej 	if (size < PAGE_SIZE)
662d470cda5Sthorpej 		size = PAGE_SIZE;
663308d7eb3Stsubai 	return size & ~PGOFSET;
664308d7eb3Stsubai }
665308d7eb3Stsubai 
6663fff0522Smacallan static int
awacs_halt_output(void * h)66793293b9eSkent awacs_halt_output(void *h)
668308d7eb3Stsubai {
66993293b9eSkent 	struct awacs_softc *sc;
670308d7eb3Stsubai 
67193293b9eSkent 	sc = h;
672308d7eb3Stsubai 	dbdma_stop(sc->sc_odma);
673308d7eb3Stsubai 	dbdma_reset(sc->sc_odma);
6747b911d03Sisaki 	sc->sc_ointr = NULL;
675308d7eb3Stsubai 	return 0;
676308d7eb3Stsubai }
677308d7eb3Stsubai 
6783fff0522Smacallan static int
awacs_halt_input(void * h)67993293b9eSkent awacs_halt_input(void *h)
680308d7eb3Stsubai {
68193293b9eSkent 	struct awacs_softc *sc;
682308d7eb3Stsubai 
68393293b9eSkent 	sc = h;
684308d7eb3Stsubai 	dbdma_stop(sc->sc_idma);
685308d7eb3Stsubai 	dbdma_reset(sc->sc_idma);
6867b911d03Sisaki 	sc->sc_iintr = NULL;
687308d7eb3Stsubai 	return 0;
688308d7eb3Stsubai }
689308d7eb3Stsubai 
6903fff0522Smacallan static int
awacs_getdev(void * h,struct audio_device * retp)69193293b9eSkent awacs_getdev(void *h, struct audio_device *retp)
692308d7eb3Stsubai {
69393293b9eSkent 
694308d7eb3Stsubai 	*retp = awacs_device;
695308d7eb3Stsubai 	return 0;
696308d7eb3Stsubai }
697308d7eb3Stsubai 
698308d7eb3Stsubai enum {
699a9e0aabfStsubai 	AWACS_MONITOR_CLASS,
700a9e0aabfStsubai 	AWACS_OUTPUT_CLASS,
701a9e0aabfStsubai 	AWACS_RECORD_CLASS,
702308d7eb3Stsubai 	AWACS_OUTPUT_SELECT,
7033fff0522Smacallan 	AWACS_VOL_MASTER,
7040a4ef18aSwiz 	AWACS_INPUT_SELECT,
7050a4ef18aSwiz 	AWACS_VOL_INPUT,
7061f070ed9Smacallan 	AWACS_VOL_MONITOR,
7070990226cSmacallan 	AWACS_BASS,
7080990226cSmacallan 	AWACS_TREBLE,
709308d7eb3Stsubai 	AWACS_ENUM_LAST
710308d7eb3Stsubai };
711308d7eb3Stsubai 
7123fff0522Smacallan static int
awacs_set_port(void * h,mixer_ctrl_t * mc)71393293b9eSkent awacs_set_port(void *h, mixer_ctrl_t *mc)
714308d7eb3Stsubai {
71593293b9eSkent 	struct awacs_softc *sc;
716308d7eb3Stsubai 	int l, r;
717308d7eb3Stsubai 
718308d7eb3Stsubai 	DPRINTF("awacs_set_port dev = %d, type = %d\n", mc->dev, mc->type);
71993293b9eSkent 	sc = h;
720308d7eb3Stsubai 	l = mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT];
721308d7eb3Stsubai 	r = mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT];
722308d7eb3Stsubai 
723308d7eb3Stsubai 	switch (mc->dev) {
724308d7eb3Stsubai 	case AWACS_OUTPUT_SELECT:
725a9e0aabfStsubai 		/* No change necessary? */
726ec35b8d0Swiz 		if (mc->un.mask == sc->sc_output_mask)
727ec35b8d0Swiz 			return 0;
7280990226cSmacallan 		awacs_select_output(sc, mc->un.mask);
729308d7eb3Stsubai 		return 0;
730308d7eb3Stsubai 
7313fff0522Smacallan 	case AWACS_VOL_MASTER:
7323fff0522Smacallan 		awacs_set_volume(sc, l, r);
733308d7eb3Stsubai 		return 0;
7340a4ef18aSwiz 
7350a4ef18aSwiz 	case AWACS_INPUT_SELECT:
7360a4ef18aSwiz 		/* no change necessary? */
7370a4ef18aSwiz 		if (mc->un.mask == sc->sc_record_source)
7380a4ef18aSwiz 			return 0;
7390a4ef18aSwiz 		switch (mc->un.mask) {
7400a4ef18aSwiz 		case 1 << 0: /* CD */
7410a4ef18aSwiz 			sc->sc_codecctl0 &= ~AWACS_INPUT_MASK;
7420a4ef18aSwiz 			sc->sc_codecctl0 |= AWACS_INPUT_CD;
7430a4ef18aSwiz 			awacs_write_codec(sc, sc->sc_codecctl0);
7440a4ef18aSwiz 			break;
7450a4ef18aSwiz 		case 1 << 1: /* microphone */
7460a4ef18aSwiz 			sc->sc_codecctl0 &= ~AWACS_INPUT_MASK;
7470a4ef18aSwiz 			sc->sc_codecctl0 |= AWACS_INPUT_MICROPHONE;
7480a4ef18aSwiz 			awacs_write_codec(sc, sc->sc_codecctl0);
7490a4ef18aSwiz 			break;
7500a4ef18aSwiz 		case 1 << 2: /* line in */
7510a4ef18aSwiz 			sc->sc_codecctl0 &= ~AWACS_INPUT_MASK;
7520a4ef18aSwiz 			sc->sc_codecctl0 |= AWACS_INPUT_LINE;
7530a4ef18aSwiz 			awacs_write_codec(sc, sc->sc_codecctl0);
7540a4ef18aSwiz 			break;
7550a4ef18aSwiz 		default: /* invalid argument */
756a9e0aabfStsubai 			return EINVAL;
7570a4ef18aSwiz 		}
7580a4ef18aSwiz 		sc->sc_record_source = mc->un.mask;
7590a4ef18aSwiz 		return 0;
760a9e0aabfStsubai 
761a9e0aabfStsubai 	case AWACS_VOL_INPUT:
762a9e0aabfStsubai 		sc->sc_codecctl0 &= ~0xff;
763a9e0aabfStsubai 		sc->sc_codecctl0 |= (l & 0xf0) | (r >> 4);
764a9e0aabfStsubai 		awacs_write_codec(sc, sc->sc_codecctl0);
765a9e0aabfStsubai 		return 0;
7660990226cSmacallan 
7671f070ed9Smacallan 	case AWACS_VOL_MONITOR:
7681f070ed9Smacallan 		awacs_set_loopthrough_volume(sc, l, r);
7691f070ed9Smacallan 		return 0;
7701f070ed9Smacallan 
7710990226cSmacallan #if NSGSMIX > 0
7720990226cSmacallan 	case AWACS_BASS:
7730990226cSmacallan 		awacs_set_bass(sc, l);
7740990226cSmacallan 		return 0;
7750990226cSmacallan 
7760990226cSmacallan 	case AWACS_TREBLE:
7770990226cSmacallan 		awacs_set_treble(sc, l);
7780990226cSmacallan 		return 0;
7790990226cSmacallan #endif
780308d7eb3Stsubai 	}
781308d7eb3Stsubai 
782308d7eb3Stsubai 	return ENXIO;
783308d7eb3Stsubai }
784308d7eb3Stsubai 
7853fff0522Smacallan static int
awacs_get_port(void * h,mixer_ctrl_t * mc)78693293b9eSkent awacs_get_port(void *h, mixer_ctrl_t *mc)
787308d7eb3Stsubai {
78893293b9eSkent 	struct awacs_softc *sc;
7890990226cSmacallan 	int l, r, vol;
790308d7eb3Stsubai 
79193293b9eSkent 	sc = h;
792308d7eb3Stsubai 	switch (mc->dev) {
793308d7eb3Stsubai 	case AWACS_OUTPUT_SELECT:
794308d7eb3Stsubai 		mc->un.mask = sc->sc_output_mask;
795308d7eb3Stsubai 		return 0;
796308d7eb3Stsubai 
7973fff0522Smacallan 	case AWACS_VOL_MASTER:
7983fff0522Smacallan 		mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] = sc->vol_l;
7993fff0522Smacallan 		mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = sc->vol_r;
8000a4ef18aSwiz 		return 0;
8010a4ef18aSwiz 
8020a4ef18aSwiz 	case AWACS_INPUT_SELECT:
8030a4ef18aSwiz 		mc->un.mask = sc->sc_record_source;
8040a4ef18aSwiz 		return 0;
8050a4ef18aSwiz 
8060a4ef18aSwiz 	case AWACS_VOL_INPUT:
8070a4ef18aSwiz 		vol = sc->sc_codecctl0 & 0xff;
8080a4ef18aSwiz 		l = (vol & 0xf0);
8090a4ef18aSwiz 		r = (vol & 0x0f) << 4;
810308d7eb3Stsubai 		mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] = l;
811308d7eb3Stsubai 		mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = r;
812308d7eb3Stsubai 		return 0;
8131f070ed9Smacallan 
8141f070ed9Smacallan 	case AWACS_VOL_MONITOR:
8151f070ed9Smacallan 		vol = sc->sc_codecctl5 & 0x3cf;
8161f070ed9Smacallan 		l = (vol & 0x3c0) >> 6;
8171f070ed9Smacallan 		r = vol & 0xf;
8181f070ed9Smacallan 		mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] = (15 - l) << 4;
8191f070ed9Smacallan 		mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = (15 - r) << 4;
8201f070ed9Smacallan 		return 0;
8211f070ed9Smacallan 
8220990226cSmacallan #if NSGSMIX > 0
8230990226cSmacallan 	case AWACS_BASS:
8240990226cSmacallan 		mc->un.value.level[AUDIO_MIXER_LEVEL_MONO] = sc->sc_bass;
8250990226cSmacallan 		return 0;
8260990226cSmacallan 
8270990226cSmacallan 	case AWACS_TREBLE:
8280990226cSmacallan 		mc->un.value.level[AUDIO_MIXER_LEVEL_MONO] = sc->sc_treble;
8290990226cSmacallan 		return 0;
8300990226cSmacallan #endif
831308d7eb3Stsubai 
832308d7eb3Stsubai 	default:
833308d7eb3Stsubai 		return ENXIO;
834308d7eb3Stsubai 	}
835308d7eb3Stsubai 
836308d7eb3Stsubai 	return 0;
837308d7eb3Stsubai }
838308d7eb3Stsubai 
8393fff0522Smacallan static int
awacs_query_devinfo(void * h,mixer_devinfo_t * dip)84093293b9eSkent awacs_query_devinfo(void *h, mixer_devinfo_t *dip)
841308d7eb3Stsubai {
8420990226cSmacallan #if NSGSMIX > 0
8430990226cSmacallan 	struct awacs_softc *sc = h;
8440990226cSmacallan #endif
845308d7eb3Stsubai 
846308d7eb3Stsubai 	switch (dip->index) {
847308d7eb3Stsubai 
848308d7eb3Stsubai 	case AWACS_OUTPUT_SELECT:
84948d31d1eSmacallan 		dip->mixer_class = AWACS_OUTPUT_CLASS;
850308d7eb3Stsubai 		strcpy(dip->label.name, AudioNoutput);
851308d7eb3Stsubai 		dip->type = AUDIO_MIXER_SET;
852308d7eb3Stsubai 		dip->prev = dip->next = AUDIO_MIXER_LAST;
853308d7eb3Stsubai 		dip->un.s.num_mem = 2;
854308d7eb3Stsubai 		strcpy(dip->un.s.member[0].label.name, AudioNspeaker);
855308d7eb3Stsubai 		dip->un.s.member[0].mask = 1 << 0;
856308d7eb3Stsubai 		strcpy(dip->un.s.member[1].label.name, AudioNheadphone);
857308d7eb3Stsubai 		dip->un.s.member[1].mask = 1 << 1;
858308d7eb3Stsubai 		return 0;
859308d7eb3Stsubai 
8603fff0522Smacallan 	case AWACS_VOL_MASTER:
86148d31d1eSmacallan 		dip->mixer_class = AWACS_OUTPUT_CLASS;
8623fff0522Smacallan 		strcpy(dip->label.name, AudioNmaster);
863308d7eb3Stsubai 		dip->type = AUDIO_MIXER_VALUE;
864308d7eb3Stsubai 		dip->prev = dip->next = AUDIO_MIXER_LAST;
865308d7eb3Stsubai 		dip->un.v.num_channels = 2;
86648d31d1eSmacallan 		dip->un.v.delta = 16;
867308d7eb3Stsubai 		strcpy(dip->un.v.units.name, AudioNvolume);
868308d7eb3Stsubai 		return 0;
869308d7eb3Stsubai 
8701f070ed9Smacallan 	case AWACS_VOL_MONITOR:
8711f070ed9Smacallan 		dip->mixer_class = AWACS_MONITOR_CLASS;
8721f070ed9Smacallan 		strcpy(dip->label.name, AudioNmonitor);
8731f070ed9Smacallan 		dip->type = AUDIO_MIXER_VALUE;
8741f070ed9Smacallan 		dip->prev = dip->next = AUDIO_MIXER_LAST;
8751f070ed9Smacallan 		dip->un.v.num_channels = 2;
8761f070ed9Smacallan 		strcpy(dip->un.v.units.name, AudioNvolume);
8771f070ed9Smacallan 		return 0;
8781f070ed9Smacallan 
8790990226cSmacallan #if NSGSMIX > 0
8800990226cSmacallan 	case AWACS_BASS:
8810990226cSmacallan 		if (sc->sc_sgsmix == NULL)
8820990226cSmacallan 			return ENXIO;
88348d31d1eSmacallan 		dip->mixer_class = AWACS_OUTPUT_CLASS;
8840990226cSmacallan 		strcpy(dip->label.name, AudioNbass);
8850990226cSmacallan 		dip->type = AUDIO_MIXER_VALUE;
8860990226cSmacallan 		dip->prev = dip->next = AUDIO_MIXER_LAST;
8870990226cSmacallan 		dip->un.v.num_channels = 1;
8880990226cSmacallan 		strcpy(dip->un.v.units.name, AudioNbass);
8890990226cSmacallan 		return 0;
8900990226cSmacallan 
8910990226cSmacallan 	case AWACS_TREBLE:
8920990226cSmacallan 		if (sc->sc_sgsmix == NULL)
8930990226cSmacallan 			return ENXIO;
89448d31d1eSmacallan 		dip->mixer_class = AWACS_OUTPUT_CLASS;
8950990226cSmacallan 		strcpy(dip->label.name, AudioNtreble);
8960990226cSmacallan 		dip->type = AUDIO_MIXER_VALUE;
8970990226cSmacallan 		dip->prev = dip->next = AUDIO_MIXER_LAST;
8980990226cSmacallan 		dip->un.v.num_channels = 1;
8990990226cSmacallan 		strcpy(dip->un.v.units.name, AudioNtreble);
9000990226cSmacallan 		return 0;
9010990226cSmacallan #endif
9020990226cSmacallan 
9030a4ef18aSwiz 	case AWACS_INPUT_SELECT:
904a9e0aabfStsubai 		dip->mixer_class = AWACS_RECORD_CLASS;
905a9e0aabfStsubai 		strcpy(dip->label.name, AudioNsource);
9060a4ef18aSwiz 		dip->type = AUDIO_MIXER_SET;
9070a4ef18aSwiz 		dip->prev = dip->next = AUDIO_MIXER_LAST;
9080a4ef18aSwiz 		dip->un.s.num_mem = 3;
9090a4ef18aSwiz 		strcpy(dip->un.s.member[0].label.name, AudioNcd);
9100a4ef18aSwiz 		dip->un.s.member[0].mask = 1 << 0;
9110a4ef18aSwiz 		strcpy(dip->un.s.member[1].label.name, AudioNmicrophone);
9120a4ef18aSwiz 		dip->un.s.member[1].mask = 1 << 1;
9130a4ef18aSwiz 		strcpy(dip->un.s.member[2].label.name, AudioNline);
9140a4ef18aSwiz 		dip->un.s.member[2].mask = 1 << 2;
9150a4ef18aSwiz 		return 0;
9160a4ef18aSwiz 
9170a4ef18aSwiz 	case AWACS_VOL_INPUT:
918a9e0aabfStsubai 		dip->mixer_class = AWACS_RECORD_CLASS;
919a9e0aabfStsubai 		strcpy(dip->label.name, AudioNrecord);
9200a4ef18aSwiz 		dip->type = AUDIO_MIXER_VALUE;
9210a4ef18aSwiz 		dip->prev = dip->next = AUDIO_MIXER_LAST;
9220a4ef18aSwiz 		dip->un.v.num_channels = 2;
9230a4ef18aSwiz 		strcpy(dip->un.v.units.name, AudioNvolume);
9240a4ef18aSwiz 		return 0;
9250a4ef18aSwiz 
926308d7eb3Stsubai 	case AWACS_MONITOR_CLASS:
927308d7eb3Stsubai 		dip->mixer_class = AWACS_MONITOR_CLASS;
928308d7eb3Stsubai 		strcpy(dip->label.name, AudioCmonitor);
929308d7eb3Stsubai 		dip->type = AUDIO_MIXER_CLASS;
930308d7eb3Stsubai 		dip->next = dip->prev = AUDIO_MIXER_LAST;
931308d7eb3Stsubai 		return 0;
932308d7eb3Stsubai 
933308d7eb3Stsubai 	case AWACS_OUTPUT_CLASS:
934308d7eb3Stsubai 		dip->mixer_class = AWACS_OUTPUT_CLASS;
935308d7eb3Stsubai 		strcpy(dip->label.name, AudioCoutputs);
936308d7eb3Stsubai 		dip->type = AUDIO_MIXER_CLASS;
937308d7eb3Stsubai 		dip->next = dip->prev = AUDIO_MIXER_LAST;
938308d7eb3Stsubai 		return 0;
9390a4ef18aSwiz 
9400a4ef18aSwiz 	case AWACS_RECORD_CLASS:
941a9e0aabfStsubai 		dip->mixer_class = AWACS_RECORD_CLASS;
9420a4ef18aSwiz 		strcpy(dip->label.name, AudioCrecord);
9430a4ef18aSwiz 		dip->type = AUDIO_MIXER_CLASS;
9440a4ef18aSwiz 		dip->next = dip->prev = AUDIO_MIXER_LAST;
9450a4ef18aSwiz 		return 0;
946308d7eb3Stsubai 	}
947308d7eb3Stsubai 
948308d7eb3Stsubai 	return ENXIO;
949308d7eb3Stsubai }
950308d7eb3Stsubai 
9513fff0522Smacallan static size_t
awacs_round_buffersize(void * h,int dir,size_t size)95293293b9eSkent awacs_round_buffersize(void *h, int dir, size_t size)
953308d7eb3Stsubai {
95493293b9eSkent 
955308d7eb3Stsubai 	if (size > 65536)
956308d7eb3Stsubai 		size = 65536;
957308d7eb3Stsubai 	return size;
958308d7eb3Stsubai }
959308d7eb3Stsubai 
9603fff0522Smacallan static int
awacs_get_props(void * h)96193293b9eSkent awacs_get_props(void *h)
962308d7eb3Stsubai {
963ede47d01Sisaki 
964ede47d01Sisaki 	return AUDIO_PROP_PLAYBACK | AUDIO_PROP_CAPTURE |
965ede47d01Sisaki 	    AUDIO_PROP_FULLDUPLEX;
966308d7eb3Stsubai }
967308d7eb3Stsubai 
9683fff0522Smacallan static int
awacs_trigger_output(void * h,void * start,void * end,int bsize,void (* intr)(void *),void * arg,const audio_params_t * param)96993293b9eSkent awacs_trigger_output(void *h, void *start, void *end, int bsize,
97093293b9eSkent 		     void (*intr)(void *), void *arg,
97193293b9eSkent 		     const audio_params_t *param)
972308d7eb3Stsubai {
97393293b9eSkent 	struct awacs_softc *sc;
97493293b9eSkent 	struct dbdma_command *cmd;
975308d7eb3Stsubai 	vaddr_t va;
976308d7eb3Stsubai 	int i, len, intmode;
977308d7eb3Stsubai 
978308d7eb3Stsubai 	DPRINTF("trigger_output %p %p 0x%x\n", start, end, bsize);
97993293b9eSkent 	sc = h;
98093293b9eSkent 	cmd = sc->sc_odmacmd;
981308d7eb3Stsubai 	sc->sc_ointr = intr;
982308d7eb3Stsubai 	sc->sc_oarg = arg;
983d470cda5Sthorpej 	sc->sc_opages = ((char *)end - (char *)start) / PAGE_SIZE;
984308d7eb3Stsubai 
985308d7eb3Stsubai #ifdef DIAGNOSTIC
986308d7eb3Stsubai 	if (sc->sc_opages > 16)
987308d7eb3Stsubai 		panic("awacs_trigger_output");
988308d7eb3Stsubai #endif
989308d7eb3Stsubai 
990308d7eb3Stsubai 	va = (vaddr_t)start;
991308d7eb3Stsubai 	len = 0;
992308d7eb3Stsubai 	for (i = sc->sc_opages; i > 0; i--) {
993d470cda5Sthorpej 		len += PAGE_SIZE;
994308d7eb3Stsubai 		if (len < bsize)
995308d7eb3Stsubai 			intmode = DBDMA_INT_NEVER;
996308d7eb3Stsubai 		else {
997308d7eb3Stsubai 			len = 0;
998308d7eb3Stsubai 			intmode = DBDMA_INT_ALWAYS;
999308d7eb3Stsubai 		}
1000308d7eb3Stsubai 
1001d470cda5Sthorpej 		DBDMA_BUILD(cmd, DBDMA_CMD_OUT_MORE, 0, PAGE_SIZE, vtophys(va),
1002308d7eb3Stsubai 			intmode, DBDMA_WAIT_NEVER, DBDMA_BRANCH_NEVER);
1003d470cda5Sthorpej 		va += PAGE_SIZE;
1004308d7eb3Stsubai 		cmd++;
1005308d7eb3Stsubai 	}
1006308d7eb3Stsubai 
1007308d7eb3Stsubai 	DBDMA_BUILD(cmd, DBDMA_CMD_NOP, 0, 0, 0,
1008308d7eb3Stsubai 		DBDMA_INT_NEVER, DBDMA_WAIT_NEVER, DBDMA_BRANCH_ALWAYS);
1009d974db0aSgarbled 	out32rb(&cmd->d_cmddep, vtophys((vaddr_t)sc->sc_odmacmd));
1010308d7eb3Stsubai 
1011308d7eb3Stsubai 	dbdma_start(sc->sc_odma, sc->sc_odmacmd);
1012308d7eb3Stsubai 
1013308d7eb3Stsubai 	return 0;
1014308d7eb3Stsubai }
1015308d7eb3Stsubai 
10163fff0522Smacallan static int
awacs_trigger_input(void * h,void * start,void * end,int bsize,void (* intr)(void *),void * arg,const audio_params_t * param)101793293b9eSkent awacs_trigger_input(void *h, void *start, void *end, int bsize,
101893293b9eSkent 		    void (*intr)(void *), void *arg,
101993293b9eSkent 		    const audio_params_t *param)
1020308d7eb3Stsubai {
1021308d7eb3Stsubai 
10223fff0522Smacallan 	DPRINTF("awacs_trigger_input called\n");
1023308d7eb3Stsubai 	return 1;
1024308d7eb3Stsubai }
1025308d7eb3Stsubai 
10263fff0522Smacallan static void
awacs_get_locks(void * opaque,kmutex_t ** intr,kmutex_t ** thread)10278a962f23Sjmcneill awacs_get_locks(void *opaque, kmutex_t **intr, kmutex_t **thread)
10288a962f23Sjmcneill {
10298a962f23Sjmcneill 	struct awacs_softc *sc = opaque;
10308a962f23Sjmcneill 
10318a962f23Sjmcneill 	*intr = &sc->sc_intr_lock;
10328a962f23Sjmcneill 	*thread = &sc->sc_lock;
10338a962f23Sjmcneill }
10348a962f23Sjmcneill 
10358a962f23Sjmcneill static void
awacs_select_output(struct awacs_softc * sc,int mask)10360990226cSmacallan awacs_select_output(struct awacs_softc *sc, int mask)
10370990226cSmacallan {
10380990226cSmacallan 
10390990226cSmacallan #if NSGSMIX > 0
10400990226cSmacallan 	if (sc->sc_sgsmix) {
10410990226cSmacallan 		if (mask & OUTPUT_HEADPHONES) {
10420990226cSmacallan 			/* mute speakers */
10430990226cSmacallan 			sgsmix_set_speaker_vol(sc->sc_sgsmix, 0, 0);
10440990226cSmacallan 			sgsmix_set_headphone_vol(sc->sc_sgsmix,
10453fff0522Smacallan 			    sc->vol_l, sc->vol_r);
10460990226cSmacallan 		}
10470990226cSmacallan 		if (mask & OUTPUT_SPEAKER) {
10480990226cSmacallan 			/* mute headphones */
10490990226cSmacallan 			sgsmix_set_speaker_vol(sc->sc_sgsmix,
10503fff0522Smacallan 			    sc->vol_l, sc->vol_r);
10510990226cSmacallan 			sgsmix_set_headphone_vol(sc->sc_sgsmix, 0, 0);
10520990226cSmacallan 		}
10530990226cSmacallan 	} else {
10540990226cSmacallan #endif
10550990226cSmacallan 	sc->sc_codecctl1 |= AWACS_MUTE_SPEAKER | AWACS_MUTE_HEADPHONE;
10563fff0522Smacallan 	if ((sc->vol_l > 0) || (sc->vol_r > 0)) {
10573fff0522Smacallan 		if (mask & OUTPUT_SPEAKER)
10580990226cSmacallan 			sc->sc_codecctl1 &= ~AWACS_MUTE_SPEAKER;
10593fff0522Smacallan 		if (mask & OUTPUT_HEADPHONES)
10600990226cSmacallan 			sc->sc_codecctl1 &= ~AWACS_MUTE_HEADPHONE;
10613fff0522Smacallan 	}
10620990226cSmacallan 	awacs_write_codec(sc, sc->sc_codecctl1);
10630990226cSmacallan #if NSGSMIX > 0
10640990226cSmacallan 	}
10650990226cSmacallan #endif
10660990226cSmacallan 	sc->sc_output_mask = mask;
10670990226cSmacallan }
10680990226cSmacallan 
10693fff0522Smacallan static void
awacs_set_speaker_volume(struct awacs_softc * sc,int left,int right)107093293b9eSkent awacs_set_speaker_volume(struct awacs_softc *sc, int left, int right)
1071308d7eb3Stsubai {
10720990226cSmacallan 
10730990226cSmacallan #if NSGSMIX > 0
10740990226cSmacallan 	if (sc->sc_sgsmix) {
10750990226cSmacallan 		if (sc->sc_output_mask & OUTPUT_SPEAKER)
10760990226cSmacallan 			sgsmix_set_speaker_vol(sc->sc_sgsmix, left, right);
10770990226cSmacallan 	} else
10780990226cSmacallan #endif
10790990226cSmacallan 	{
108093293b9eSkent 		int lval;
108193293b9eSkent 		int rval;
10823fff0522Smacallan 		uint32_t codecctl = sc->sc_codecctl1;
1083308d7eb3Stsubai 
10843fff0522Smacallan 		lval = 15 - ((left  & 0xf0) >> 4);
10853fff0522Smacallan 		rval = 15 - ((right & 0xf0) >> 4);
1086308d7eb3Stsubai 		DPRINTF("speaker_volume %d %d\n", lval, rval);
1087308d7eb3Stsubai 
1088308d7eb3Stsubai 		sc->sc_codecctl4 &= ~0x3cf;
1089308d7eb3Stsubai 		sc->sc_codecctl4 |= (lval << 6) | rval;
1090308d7eb3Stsubai 		awacs_write_codec(sc, sc->sc_codecctl4);
10913fff0522Smacallan 		if ((left == 0) && (right == 0)) {
10923fff0522Smacallan 			/*
10933fff0522Smacallan 			 * max. attenuation doesn't mean silence so we need to
10943fff0522Smacallan 			 * mute the output channel here
10953fff0522Smacallan 			 */
10963fff0522Smacallan 			codecctl |= AWACS_MUTE_SPEAKER;
10973fff0522Smacallan 		} else if (sc->sc_output_mask & OUTPUT_SPEAKER) {
10983fff0522Smacallan 			codecctl &= ~AWACS_MUTE_SPEAKER;
10990990226cSmacallan 		}
1100308d7eb3Stsubai 
11013fff0522Smacallan 		if (codecctl != sc->sc_codecctl1) {
11023fff0522Smacallan 
11033fff0522Smacallan 			sc->sc_codecctl1 = codecctl;
11043fff0522Smacallan 			awacs_write_codec(sc, sc->sc_codecctl1);
11053fff0522Smacallan 		}
11063fff0522Smacallan 	}
11073fff0522Smacallan }
11083fff0522Smacallan 
11093fff0522Smacallan static void
awacs_set_ext_volume(struct awacs_softc * sc,int left,int right)111093293b9eSkent awacs_set_ext_volume(struct awacs_softc *sc, int left, int right)
1111308d7eb3Stsubai {
11120990226cSmacallan 
11130990226cSmacallan #if NSGSMIX > 0
11140990226cSmacallan 	if (sc->sc_sgsmix) {
11150990226cSmacallan 		if (sc->sc_output_mask & OUTPUT_HEADPHONES)
11160990226cSmacallan 			sgsmix_set_headphone_vol(sc->sc_sgsmix, left, right);
11170990226cSmacallan 	} else
11180990226cSmacallan #endif
11190990226cSmacallan 	{
112093293b9eSkent 		int lval;
112193293b9eSkent 		int rval;
11223fff0522Smacallan 		uint32_t codecctl = sc->sc_codecctl1;
1123308d7eb3Stsubai 
11243fff0522Smacallan 		lval = 15 - ((left  & 0xf0) >> 4);
11253fff0522Smacallan 		rval = 15 - ((right & 0xf0) >> 4);
1126308d7eb3Stsubai 		DPRINTF("ext_volume %d %d\n", lval, rval);
1127308d7eb3Stsubai 
1128308d7eb3Stsubai 		sc->sc_codecctl2 &= ~0x3cf;
1129308d7eb3Stsubai 		sc->sc_codecctl2 |= (lval << 6) | rval;
1130308d7eb3Stsubai 		awacs_write_codec(sc, sc->sc_codecctl2);
11313fff0522Smacallan 
11323fff0522Smacallan 		if ((left == 0) && (right == 0)) {
11333fff0522Smacallan 			/*
11343fff0522Smacallan 			 * max. attenuation doesn't mean silence so we need to
11353fff0522Smacallan 			 * mute the output channel here
11363fff0522Smacallan 			 */
11373fff0522Smacallan 			codecctl |= AWACS_MUTE_HEADPHONE;
11383fff0522Smacallan 		} else if (sc->sc_output_mask & OUTPUT_HEADPHONES) {
11393fff0522Smacallan 
11403fff0522Smacallan 			codecctl &= ~AWACS_MUTE_HEADPHONE;
1141308d7eb3Stsubai 		}
11423fff0522Smacallan 
11433fff0522Smacallan 		if (codecctl != sc->sc_codecctl1) {
11443fff0522Smacallan 
11453fff0522Smacallan 			sc->sc_codecctl1 = codecctl;
11463fff0522Smacallan 			awacs_write_codec(sc, sc->sc_codecctl1);
11473fff0522Smacallan 		}
11483fff0522Smacallan 	}
11493fff0522Smacallan }
11503fff0522Smacallan 
1151b6c6870cSmacallan void
awacs_set_volume(struct awacs_softc * sc,int left,int right)11523fff0522Smacallan awacs_set_volume(struct awacs_softc *sc, int left, int right)
11533fff0522Smacallan {
11543fff0522Smacallan 
11553fff0522Smacallan 	awacs_set_ext_volume(sc, left, right);
11563fff0522Smacallan 	awacs_set_speaker_volume(sc, left, right);
11573fff0522Smacallan 
11583fff0522Smacallan 	sc->vol_l = left;
11593fff0522Smacallan 	sc->vol_r = right;
11600990226cSmacallan }
11610990226cSmacallan 
11620990226cSmacallan #if NSGSMIX > 0
11630990226cSmacallan static void
awacs_set_bass(struct awacs_softc * sc,int bass)11640990226cSmacallan awacs_set_bass(struct awacs_softc *sc, int bass)
11650990226cSmacallan {
11660990226cSmacallan 
11670990226cSmacallan 	if (sc->sc_bass == bass)
11680990226cSmacallan 		return;
11690990226cSmacallan 
11700990226cSmacallan 	sc->sc_bass = bass;
11710990226cSmacallan 	if (sc->sc_sgsmix)
1172b6c6870cSmacallan 		sgsmix_set_bass_treble(sc->sc_sgsmix, sc->sc_bass,
1173b6c6870cSmacallan 		    sc->sc_treble);
11740990226cSmacallan }
11750990226cSmacallan 
11760990226cSmacallan static void
awacs_set_treble(struct awacs_softc * sc,int treble)11770990226cSmacallan awacs_set_treble(struct awacs_softc *sc, int treble)
11780990226cSmacallan {
11790990226cSmacallan 
11800990226cSmacallan 	if (sc->sc_treble == treble)
11810990226cSmacallan 		return;
11820990226cSmacallan 
11830990226cSmacallan 	sc->sc_treble = treble;
11840990226cSmacallan 	if (sc->sc_sgsmix)
1185b6c6870cSmacallan 		sgsmix_set_bass_treble(sc->sc_sgsmix, sc->sc_bass,
1186b6c6870cSmacallan 		    sc->sc_treble);
11870990226cSmacallan }
11880990226cSmacallan #endif
11890990226cSmacallan 
11900990226cSmacallan void
awacs_set_loopthrough_volume(struct awacs_softc * sc,int left,int right)11910990226cSmacallan awacs_set_loopthrough_volume(struct awacs_softc *sc, int left, int right)
11920990226cSmacallan {
11930990226cSmacallan 	int lval;
11940990226cSmacallan 	int rval;
11950990226cSmacallan 
11963fff0522Smacallan 	lval = 15 - ((left  & 0xff) >> 4);
11973fff0522Smacallan 	rval = 15 - ((right & 0xff) >> 4);
11980990226cSmacallan 	DPRINTF("loopthrough_volume %d %d\n", lval, rval);
11990990226cSmacallan 
12000990226cSmacallan 	sc->sc_codecctl5 &= ~0x3cf;
12010990226cSmacallan 	sc->sc_codecctl5 |= (lval << 6) | rval;
12020990226cSmacallan 	awacs_write_codec(sc, sc->sc_codecctl5);
12030990226cSmacallan }
1204308d7eb3Stsubai 
1205308d7eb3Stsubai int
awacs_set_rate(struct awacs_softc * sc,const audio_params_t * p)120693293b9eSkent awacs_set_rate(struct awacs_softc *sc, const audio_params_t *p)
1207308d7eb3Stsubai {
1208308d7eb3Stsubai 	int c;
1209308d7eb3Stsubai 
121065cd27e4Swiz 	switch (p->sample_rate) {
1211308d7eb3Stsubai 	case 44100:
1212308d7eb3Stsubai 		c = AWACS_RATE_44100;
1213308d7eb3Stsubai 		break;
1214308d7eb3Stsubai 	case 29400:
1215308d7eb3Stsubai 		c = AWACS_RATE_29400;
1216308d7eb3Stsubai 		break;
1217308d7eb3Stsubai 	case 22050:
1218308d7eb3Stsubai 		c = AWACS_RATE_22050;
1219308d7eb3Stsubai 		break;
1220308d7eb3Stsubai 	case 17640:
1221308d7eb3Stsubai 		c = AWACS_RATE_17640;
1222308d7eb3Stsubai 		break;
1223308d7eb3Stsubai 	case 14700:
1224308d7eb3Stsubai 		c = AWACS_RATE_14700;
1225308d7eb3Stsubai 		break;
1226308d7eb3Stsubai 	case 11025:
1227308d7eb3Stsubai 		c = AWACS_RATE_11025;
1228308d7eb3Stsubai 		break;
1229308d7eb3Stsubai 	case 8820:
1230308d7eb3Stsubai 		c = AWACS_RATE_8820;
1231308d7eb3Stsubai 		break;
1232308d7eb3Stsubai 	case 7350:
1233308d7eb3Stsubai 		c = AWACS_RATE_7350;
1234308d7eb3Stsubai 		break;
1235308d7eb3Stsubai 	default:
1236308d7eb3Stsubai 		return -1;
1237308d7eb3Stsubai 	}
1238308d7eb3Stsubai 
1239308d7eb3Stsubai 	sc->sc_soundctl &= ~AWACS_RATE_MASK;
1240308d7eb3Stsubai 	sc->sc_soundctl |= c;
1241308d7eb3Stsubai 	awacs_write_reg(sc, AWACS_SOUND_CTRL, sc->sc_soundctl);
1242308d7eb3Stsubai 
1243308d7eb3Stsubai 	return 0;
1244308d7eb3Stsubai }
12453fff0522Smacallan 
12463fff0522Smacallan static int
awacs_check_headphones(struct awacs_softc * sc)12473fff0522Smacallan awacs_check_headphones(struct awacs_softc *sc)
12483fff0522Smacallan {
12493fff0522Smacallan 	uint32_t reg;
12503fff0522Smacallan 	reg = awacs_read_reg(sc, AWACS_CODEC_STATUS);
1251b6c6870cSmacallan 	DPRINTF("%s: codec status reg %08x\n", device_xname(sc->sc_dev), reg);
12523fff0522Smacallan 	return ((reg & sc->sc_headphones_mask) == sc->sc_headphones_in);
12533fff0522Smacallan }
12543fff0522Smacallan 
12553fff0522Smacallan static int
awacs_status_intr(void * cookie)12563fff0522Smacallan awacs_status_intr(void *cookie)
12573fff0522Smacallan {
12583fff0522Smacallan 	struct awacs_softc *sc = cookie;
12593fff0522Smacallan 	int mask;
12603fff0522Smacallan 
12618a962f23Sjmcneill 	mutex_spin_enter(&sc->sc_intr_lock);
12623fff0522Smacallan 	mask = awacs_check_headphones(sc) ? OUTPUT_HEADPHONES : OUTPUT_SPEAKER;
12633fff0522Smacallan 	if (mask != sc->sc_output_mask) {
12643fff0522Smacallan 
12653fff0522Smacallan 		sc->sc_output_wanted = mask;
12668a962f23Sjmcneill 		cv_signal(&sc->sc_event);
12673fff0522Smacallan 	}
12683fff0522Smacallan 	/* clear the interrupt */
12693fff0522Smacallan 	awacs_write_reg(sc, AWACS_SOUND_CTRL, sc->sc_soundctl | AWACS_PORTCHG);
12708a962f23Sjmcneill 	mutex_spin_exit(&sc->sc_intr_lock);
12713fff0522Smacallan 	return 1;
12723fff0522Smacallan }
12733fff0522Smacallan 
12743fff0522Smacallan static void
awacs_thread(void * cookie)12753fff0522Smacallan awacs_thread(void *cookie)
12763fff0522Smacallan {
12773fff0522Smacallan 	struct awacs_softc *sc = cookie;
12783fff0522Smacallan 
12793fff0522Smacallan 	while (1) {
1280*bef84f10Smacallan 		mutex_enter(&sc->sc_event_lock);
1281*bef84f10Smacallan 		cv_timedwait(&sc->sc_event, &sc->sc_event_lock, hz);
1282*bef84f10Smacallan 		mutex_exit(&sc->sc_event_lock);
12833fff0522Smacallan 		if (sc->sc_output_wanted == sc->sc_output_mask)
12843fff0522Smacallan 			continue;
12853fff0522Smacallan 
12863fff0522Smacallan 		awacs_select_output(sc, sc->sc_output_wanted);
1287b6c6870cSmacallan 		DPRINTF("%s: switching to %s\n", device_xname(sc->sc_dev),
12883fff0522Smacallan 		    (sc->sc_output_wanted & OUTPUT_SPEAKER) ?
12893fff0522Smacallan 		    "speaker" : "headphones");
12903fff0522Smacallan 	}
12913fff0522Smacallan }
1292