xref: /freebsd/sys/dev/sound/usb/uaudio.c (revision 1234097e)
13a3f90c6SAndrew Thompson /*	$NetBSD: uaudio.c,v 1.91 2004/11/05 17:46:14 kent Exp $	*/
23a3f90c6SAndrew Thompson /*	$FreeBSD$ */
33a3f90c6SAndrew Thompson 
43a3f90c6SAndrew Thompson /*-
53a3f90c6SAndrew Thompson  * Copyright (c) 1999 The NetBSD Foundation, Inc.
63a3f90c6SAndrew Thompson  * All rights reserved.
73a3f90c6SAndrew Thompson  *
83a3f90c6SAndrew Thompson  * This code is derived from software contributed to The NetBSD Foundation
93a3f90c6SAndrew Thompson  * by Lennart Augustsson (lennart@augustsson.net) at
103a3f90c6SAndrew Thompson  * Carlstedt Research & Technology.
113a3f90c6SAndrew Thompson  *
123a3f90c6SAndrew Thompson  * Redistribution and use in source and binary forms, with or without
133a3f90c6SAndrew Thompson  * modification, are permitted provided that the following conditions
143a3f90c6SAndrew Thompson  * are met:
153a3f90c6SAndrew Thompson  * 1. Redistributions of source code must retain the above copyright
163a3f90c6SAndrew Thompson  *    notice, this list of conditions and the following disclaimer.
173a3f90c6SAndrew Thompson  * 2. Redistributions in binary form must reproduce the above copyright
183a3f90c6SAndrew Thompson  *    notice, this list of conditions and the following disclaimer in the
193a3f90c6SAndrew Thompson  *    documentation and/or other materials provided with the distribution.
203a3f90c6SAndrew Thompson  *
213a3f90c6SAndrew Thompson  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
223a3f90c6SAndrew Thompson  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
233a3f90c6SAndrew Thompson  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
243a3f90c6SAndrew Thompson  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
253a3f90c6SAndrew Thompson  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
263a3f90c6SAndrew Thompson  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
273a3f90c6SAndrew Thompson  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
283a3f90c6SAndrew Thompson  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
293a3f90c6SAndrew Thompson  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
303a3f90c6SAndrew Thompson  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
313a3f90c6SAndrew Thompson  * POSSIBILITY OF SUCH DAMAGE.
323a3f90c6SAndrew Thompson  */
333a3f90c6SAndrew Thompson 
344b7ec270SMarius Strobl #include <sys/cdefs.h>
354b7ec270SMarius Strobl __FBSDID("$FreeBSD$");
364b7ec270SMarius Strobl 
373a3f90c6SAndrew Thompson /*
383a3f90c6SAndrew Thompson  * USB audio specs: http://www.usb.org/developers/devclass_docs/audio10.pdf
393a3f90c6SAndrew Thompson  *                  http://www.usb.org/developers/devclass_docs/frmts10.pdf
403a3f90c6SAndrew Thompson  *                  http://www.usb.org/developers/devclass_docs/termt10.pdf
413a3f90c6SAndrew Thompson  */
423a3f90c6SAndrew Thompson 
433a3f90c6SAndrew Thompson /*
443a3f90c6SAndrew Thompson  * Also merged:
453a3f90c6SAndrew Thompson  *  $NetBSD: uaudio.c,v 1.94 2005/01/15 15:19:53 kent Exp $
463a3f90c6SAndrew Thompson  *  $NetBSD: uaudio.c,v 1.95 2005/01/16 06:02:19 dsainty Exp $
473a3f90c6SAndrew Thompson  *  $NetBSD: uaudio.c,v 1.96 2005/01/16 12:46:00 kent Exp $
483a3f90c6SAndrew Thompson  *  $NetBSD: uaudio.c,v 1.97 2005/02/24 08:19:38 martin Exp $
493a3f90c6SAndrew Thompson  */
503a3f90c6SAndrew Thompson 
51ed6d949aSAndrew Thompson #include <sys/stdint.h>
52ed6d949aSAndrew Thompson #include <sys/stddef.h>
53ed6d949aSAndrew Thompson #include <sys/param.h>
54ed6d949aSAndrew Thompson #include <sys/queue.h>
55ed6d949aSAndrew Thompson #include <sys/types.h>
56ed6d949aSAndrew Thompson #include <sys/systm.h>
57ed6d949aSAndrew Thompson #include <sys/kernel.h>
58ed6d949aSAndrew Thompson #include <sys/bus.h>
59ed6d949aSAndrew Thompson #include <sys/module.h>
60ed6d949aSAndrew Thompson #include <sys/lock.h>
61ed6d949aSAndrew Thompson #include <sys/mutex.h>
62ed6d949aSAndrew Thompson #include <sys/condvar.h>
63ed6d949aSAndrew Thompson #include <sys/sysctl.h>
64ed6d949aSAndrew Thompson #include <sys/sx.h>
65ed6d949aSAndrew Thompson #include <sys/unistd.h>
66ed6d949aSAndrew Thompson #include <sys/callout.h>
67ed6d949aSAndrew Thompson #include <sys/malloc.h>
68ed6d949aSAndrew Thompson #include <sys/priv.h>
69ed6d949aSAndrew Thompson 
703a3f90c6SAndrew Thompson #include "usbdevs.h"
713a3f90c6SAndrew Thompson #include <dev/usb/usb.h>
72ed6d949aSAndrew Thompson #include <dev/usb/usbdi.h>
73ed6d949aSAndrew Thompson #include <dev/usb/usbdi_util.h>
743a3f90c6SAndrew Thompson 
753a3f90c6SAndrew Thompson #define	USB_DEBUG_VAR uaudio_debug
763a3f90c6SAndrew Thompson #include <dev/usb/usb_debug.h>
773a3f90c6SAndrew Thompson 
783a3f90c6SAndrew Thompson #include <dev/usb/quirk/usb_quirk.h>
793a3f90c6SAndrew Thompson 
803a3f90c6SAndrew Thompson #include <sys/reboot.h>			/* for bootverbose */
813a3f90c6SAndrew Thompson 
8290da2b28SAriff Abdullah #ifdef HAVE_KERNEL_OPTION_HEADERS
8390da2b28SAriff Abdullah #include "opt_snd.h"
8490da2b28SAriff Abdullah #endif
8590da2b28SAriff Abdullah 
863a3f90c6SAndrew Thompson #include <dev/sound/pcm/sound.h>
873a3f90c6SAndrew Thompson #include <dev/sound/usb/uaudioreg.h>
883a3f90c6SAndrew Thompson #include <dev/sound/usb/uaudio.h>
893a3f90c6SAndrew Thompson #include <dev/sound/chip.h>
903a3f90c6SAndrew Thompson #include "feeder_if.h"
913a3f90c6SAndrew Thompson 
92afbfddd9SAndrew Thompson static int uaudio_default_rate = 0;		/* use rate list */
933a3f90c6SAndrew Thompson static int uaudio_default_bits = 32;
94afbfddd9SAndrew Thompson static int uaudio_default_channels = 0;		/* use default */
953a3f90c6SAndrew Thompson 
96b850ecc1SAndrew Thompson #ifdef USB_DEBUG
973a3f90c6SAndrew Thompson static int uaudio_debug = 0;
983a3f90c6SAndrew Thompson 
996472ac3dSEd Schouten static SYSCTL_NODE(_hw_usb, OID_AUTO, uaudio, CTLFLAG_RW, 0, "USB uaudio");
100afbfddd9SAndrew Thompson 
101fadc970bSAndrew Thompson SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, debug, CTLFLAG_RW,
1023a3f90c6SAndrew Thompson     &uaudio_debug, 0, "uaudio debug level");
103afbfddd9SAndrew Thompson 
104afbfddd9SAndrew Thompson TUNABLE_INT("hw.usb.uaudio.default_rate", &uaudio_default_rate);
105fadc970bSAndrew Thompson SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_rate, CTLFLAG_RW,
1063a3f90c6SAndrew Thompson     &uaudio_default_rate, 0, "uaudio default sample rate");
107afbfddd9SAndrew Thompson 
108afbfddd9SAndrew Thompson TUNABLE_INT("hw.usb.uaudio.default_bits", &uaudio_default_bits);
109fadc970bSAndrew Thompson SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_bits, CTLFLAG_RW,
1103a3f90c6SAndrew Thompson     &uaudio_default_bits, 0, "uaudio default sample bits");
111afbfddd9SAndrew Thompson 
112afbfddd9SAndrew Thompson TUNABLE_INT("hw.usb.uaudio.default_channels", &uaudio_default_channels);
113fadc970bSAndrew Thompson SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_channels, CTLFLAG_RW,
1143a3f90c6SAndrew Thompson     &uaudio_default_channels, 0, "uaudio default sample channels");
1153a3f90c6SAndrew Thompson #endif
1163a3f90c6SAndrew Thompson 
117b029f6bbSAndrew Thompson #define	UAUDIO_NFRAMES		64	/* must be factor of 8 due HS-USB */
1183a3f90c6SAndrew Thompson #define	UAUDIO_NCHANBUFS	2	/* number of outstanding request */
119e2524b2eSHans Petter Selasky #define	UAUDIO_RECURSE_LIMIT	255	/* rounds */
1203a3f90c6SAndrew Thompson 
1213a3f90c6SAndrew Thompson #define	MAKE_WORD(h,l) (((h) << 8) | (l))
1223a3f90c6SAndrew Thompson #define	BIT_TEST(bm,bno) (((bm)[(bno) / 8] >> (7 - ((bno) % 8))) & 1)
123c737a632SAndrew Thompson #define	UAUDIO_MAX_CHAN(x) (x)
1243a3f90c6SAndrew Thompson 
125e2524b2eSHans Petter Selasky union uaudio_asid {
126e2524b2eSHans Petter Selasky 	const struct usb_audio_streaming_interface_descriptor *v1;
127e2524b2eSHans Petter Selasky 	const struct usb_audio20_streaming_interface_descriptor *v2;
128e2524b2eSHans Petter Selasky };
129e2524b2eSHans Petter Selasky 
130e2524b2eSHans Petter Selasky union uaudio_asf1d {
131e2524b2eSHans Petter Selasky 	const struct usb_audio_streaming_type1_descriptor *v1;
132e2524b2eSHans Petter Selasky 	const struct usb_audio20_streaming_type1_descriptor *v2;
133e2524b2eSHans Petter Selasky };
134e2524b2eSHans Petter Selasky 
135e2524b2eSHans Petter Selasky union uaudio_sed {
136e2524b2eSHans Petter Selasky 	const struct usb_audio_streaming_endpoint_descriptor *v1;
137e2524b2eSHans Petter Selasky 	const struct usb_audio20_streaming_endpoint_descriptor *v2;
138e2524b2eSHans Petter Selasky };
139e2524b2eSHans Petter Selasky 
1403a3f90c6SAndrew Thompson struct uaudio_mixer_node {
1413a3f90c6SAndrew Thompson 	int32_t	minval;
1423a3f90c6SAndrew Thompson 	int32_t	maxval;
1433a3f90c6SAndrew Thompson #define	MIX_MAX_CHAN 8
1443a3f90c6SAndrew Thompson 	int32_t	wValue[MIX_MAX_CHAN];	/* using nchan */
1453a3f90c6SAndrew Thompson 	uint32_t mul;
1463a3f90c6SAndrew Thompson 	uint32_t ctl;
1473a3f90c6SAndrew Thompson 
1483a3f90c6SAndrew Thompson 	uint16_t wData[MIX_MAX_CHAN];	/* using nchan */
1493a3f90c6SAndrew Thompson 	uint16_t wIndex;
1503a3f90c6SAndrew Thompson 
1513a3f90c6SAndrew Thompson 	uint8_t	update[(MIX_MAX_CHAN + 7) / 8];
1523a3f90c6SAndrew Thompson 	uint8_t	nchan;
1533a3f90c6SAndrew Thompson 	uint8_t	type;
1543a3f90c6SAndrew Thompson #define	MIX_ON_OFF	1
1553a3f90c6SAndrew Thompson #define	MIX_SIGNED_16	2
1563a3f90c6SAndrew Thompson #define	MIX_UNSIGNED_16	3
1573a3f90c6SAndrew Thompson #define	MIX_SIGNED_8	4
1583a3f90c6SAndrew Thompson #define	MIX_SELECTOR	5
1593a3f90c6SAndrew Thompson #define	MIX_UNKNOWN     6
1603a3f90c6SAndrew Thompson #define	MIX_SIZE(n) ((((n) == MIX_SIGNED_16) || \
1613a3f90c6SAndrew Thompson 		      ((n) == MIX_UNSIGNED_16)) ? 2 : 1)
1623a3f90c6SAndrew Thompson #define	MIX_UNSIGNED(n) ((n) == MIX_UNSIGNED_16)
1633a3f90c6SAndrew Thompson 
1643a3f90c6SAndrew Thompson #define	MAX_SELECTOR_INPUT_PIN 256
1653a3f90c6SAndrew Thompson 	uint8_t	slctrtype[MAX_SELECTOR_INPUT_PIN];
1663a3f90c6SAndrew Thompson 	uint8_t	class;
1673a3f90c6SAndrew Thompson 
1683a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *next;
1693a3f90c6SAndrew Thompson };
1703a3f90c6SAndrew Thompson 
1713a3f90c6SAndrew Thompson struct uaudio_chan {
1723a3f90c6SAndrew Thompson 	struct pcmchan_caps pcm_cap;	/* capabilities */
1733a3f90c6SAndrew Thompson 
1743a3f90c6SAndrew Thompson 	struct snd_dbuf *pcm_buf;
1754c21be9bSRebecca Cran 	const struct usb_config *usb_cfg;
1763a3f90c6SAndrew Thompson 	struct mtx *pcm_mtx;		/* lock protecting this structure */
1773a3f90c6SAndrew Thompson 	struct uaudio_softc *priv_sc;
1783a3f90c6SAndrew Thompson 	struct pcm_channel *pcm_ch;
179760bc48eSAndrew Thompson 	struct usb_xfer *xfer[UAUDIO_NCHANBUFS];
180e2524b2eSHans Petter Selasky 	union uaudio_asf1d p_asf1d;
181e2524b2eSHans Petter Selasky 	union uaudio_sed p_sed;
1824c21be9bSRebecca Cran 	const usb_endpoint_descriptor_audio_t *p_ed1;
1833a3f90c6SAndrew Thompson 	const struct uaudio_format *p_fmt;
1843a3f90c6SAndrew Thompson 
1853a3f90c6SAndrew Thompson 	uint8_t *buf;			/* pointer to buffer */
1863a3f90c6SAndrew Thompson 	uint8_t *start;			/* upper layer buffer start */
1873a3f90c6SAndrew Thompson 	uint8_t *end;			/* upper layer buffer end */
1883a3f90c6SAndrew Thompson 	uint8_t *cur;			/* current position in upper layer
1893a3f90c6SAndrew Thompson 					 * buffer */
1903a3f90c6SAndrew Thompson 
1913a3f90c6SAndrew Thompson 	uint32_t intr_size;		/* in bytes */
192b029f6bbSAndrew Thompson 	uint32_t intr_frames;		/* in units */
1933a3f90c6SAndrew Thompson 	uint32_t sample_rate;
194afbfddd9SAndrew Thompson 	uint32_t frames_per_second;
195afbfddd9SAndrew Thompson 	uint32_t sample_rem;
196afbfddd9SAndrew Thompson 	uint32_t sample_curr;
197afbfddd9SAndrew Thompson 
1983a3f90c6SAndrew Thompson 	uint32_t format;
1993a3f90c6SAndrew Thompson 	uint32_t pcm_format[2];
2003a3f90c6SAndrew Thompson 
201afbfddd9SAndrew Thompson 	uint16_t bytes_per_frame[2];
202afbfddd9SAndrew Thompson 
203afbfddd9SAndrew Thompson 	uint16_t sample_size;
2043a3f90c6SAndrew Thompson 
2053a3f90c6SAndrew Thompson 	uint8_t	valid;
2063a3f90c6SAndrew Thompson 	uint8_t	iface_index;
2073a3f90c6SAndrew Thompson 	uint8_t	iface_alt_index;
208e2524b2eSHans Petter Selasky 	uint8_t channels;
2093a3f90c6SAndrew Thompson };
2103a3f90c6SAndrew Thompson 
2113a3f90c6SAndrew Thompson #define	UMIDI_CABLES_MAX   16		/* units */
212e0b17a62SHans Petter Selasky #define	UMIDI_TX_FRAMES	   256		/* units */
213910f1dcfSHans Petter Selasky #define	UMIDI_TX_BUFFER    (UMIDI_TX_FRAMES * 4)	/* bytes */
2143a3f90c6SAndrew Thompson 
2156f068a43SHans Petter Selasky enum {
2166f068a43SHans Petter Selasky 	UMIDI_TX_TRANSFER,
2176f068a43SHans Petter Selasky 	UMIDI_RX_TRANSFER,
2186f068a43SHans Petter Selasky 	UMIDI_N_TRANSFER,
2196f068a43SHans Petter Selasky };
2206f068a43SHans Petter Selasky 
2213a3f90c6SAndrew Thompson struct umidi_sub_chan {
222760bc48eSAndrew Thompson 	struct usb_fifo_sc fifo;
2233a3f90c6SAndrew Thompson 	uint8_t *temp_cmd;
2243a3f90c6SAndrew Thompson 	uint8_t	temp_0[4];
2253a3f90c6SAndrew Thompson 	uint8_t	temp_1[4];
2263a3f90c6SAndrew Thompson 	uint8_t	state;
2273a3f90c6SAndrew Thompson #define	UMIDI_ST_UNKNOWN   0		/* scan for command */
2283a3f90c6SAndrew Thompson #define	UMIDI_ST_1PARAM    1
2293a3f90c6SAndrew Thompson #define	UMIDI_ST_2PARAM_1  2
2303a3f90c6SAndrew Thompson #define	UMIDI_ST_2PARAM_2  3
2313a3f90c6SAndrew Thompson #define	UMIDI_ST_SYSEX_0   4
2323a3f90c6SAndrew Thompson #define	UMIDI_ST_SYSEX_1   5
2333a3f90c6SAndrew Thompson #define	UMIDI_ST_SYSEX_2   6
2343a3f90c6SAndrew Thompson 
2353a3f90c6SAndrew Thompson 	uint8_t	read_open:1;
2363a3f90c6SAndrew Thompson 	uint8_t	write_open:1;
2373a3f90c6SAndrew Thompson 	uint8_t	unused:6;
2383a3f90c6SAndrew Thompson };
2393a3f90c6SAndrew Thompson 
2403a3f90c6SAndrew Thompson struct umidi_chan {
2413a3f90c6SAndrew Thompson 
2423a3f90c6SAndrew Thompson 	struct umidi_sub_chan sub[UMIDI_CABLES_MAX];
2433a3f90c6SAndrew Thompson 	struct mtx mtx;
2443a3f90c6SAndrew Thompson 
245760bc48eSAndrew Thompson 	struct usb_xfer *xfer[UMIDI_N_TRANSFER];
2463a3f90c6SAndrew Thompson 
2473a3f90c6SAndrew Thompson 	uint8_t	iface_index;
2483a3f90c6SAndrew Thompson 	uint8_t	iface_alt_index;
2493a3f90c6SAndrew Thompson 
2503a3f90c6SAndrew Thompson 	uint8_t	read_open_refcount;
2513a3f90c6SAndrew Thompson 	uint8_t	write_open_refcount;
2523a3f90c6SAndrew Thompson 
2533a3f90c6SAndrew Thompson 	uint8_t	curr_cable;
2543a3f90c6SAndrew Thompson 	uint8_t	max_cable;
2553a3f90c6SAndrew Thompson 	uint8_t	valid;
2564944c3a8SHans Petter Selasky 	uint8_t single_command;
2573a3f90c6SAndrew Thompson };
2583a3f90c6SAndrew Thompson 
259e2524b2eSHans Petter Selasky struct uaudio_search_result {
260e2524b2eSHans Petter Selasky 	uint8_t	bit_input[(256 + 7) / 8];
261e2524b2eSHans Petter Selasky 	uint8_t	bit_output[(256 + 7) / 8];
262e2524b2eSHans Petter Selasky 	uint8_t	recurse_level;
263e2524b2eSHans Petter Selasky 	uint8_t	id_max;
264e2524b2eSHans Petter Selasky 	uint8_t is_input;
265e2524b2eSHans Petter Selasky };
266e2524b2eSHans Petter Selasky 
2673a3f90c6SAndrew Thompson struct uaudio_softc {
2683a3f90c6SAndrew Thompson 	struct sbuf sc_sndstat;
2693a3f90c6SAndrew Thompson 	struct sndcard_func sc_sndcard_func;
2703a3f90c6SAndrew Thompson 	struct uaudio_chan sc_rec_chan;
2713a3f90c6SAndrew Thompson 	struct uaudio_chan sc_play_chan;
2723a3f90c6SAndrew Thompson 	struct umidi_chan sc_midi_chan;
273e2524b2eSHans Petter Selasky 	struct uaudio_search_result sc_mixer_clocks;
2743a3f90c6SAndrew Thompson 
275760bc48eSAndrew Thompson 	struct usb_device *sc_udev;
276760bc48eSAndrew Thompson 	struct usb_xfer *sc_mixer_xfer[1];
2773a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *sc_mixer_root;
2783a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *sc_mixer_curr;
2793a3f90c6SAndrew Thompson 
2803a3f90c6SAndrew Thompson 	uint32_t sc_mix_info;
2813a3f90c6SAndrew Thompson 	uint32_t sc_recsrc_info;
2823a3f90c6SAndrew Thompson 
2833a3f90c6SAndrew Thompson 	uint16_t sc_audio_rev;
2843a3f90c6SAndrew Thompson 	uint16_t sc_mixer_count;
2853a3f90c6SAndrew Thompson 
2863a3f90c6SAndrew Thompson 	uint8_t	sc_sndstat_valid;
2873a3f90c6SAndrew Thompson 	uint8_t	sc_mixer_iface_index;
2883a3f90c6SAndrew Thompson 	uint8_t	sc_mixer_iface_no;
2893a3f90c6SAndrew Thompson 	uint8_t	sc_mixer_chan;
2903a3f90c6SAndrew Thompson 	uint8_t	sc_pcm_registered:1;
2913a3f90c6SAndrew Thompson 	uint8_t	sc_mixer_init:1;
2923a3f90c6SAndrew Thompson 	uint8_t	sc_uq_audio_swap_lr:1;
2933a3f90c6SAndrew Thompson 	uint8_t	sc_uq_au_inp_async:1;
2943a3f90c6SAndrew Thompson 	uint8_t	sc_uq_au_no_xu:1;
2953a3f90c6SAndrew Thompson 	uint8_t	sc_uq_bad_adc:1;
29625b74dabSHans Petter Selasky 	uint8_t	sc_uq_au_vendor_class:1;
2973a3f90c6SAndrew Thompson };
2983a3f90c6SAndrew Thompson 
2993a3f90c6SAndrew Thompson struct uaudio_terminal_node {
3003a3f90c6SAndrew Thompson 	union {
301760bc48eSAndrew Thompson 		const struct usb_descriptor *desc;
302e2524b2eSHans Petter Selasky 		const struct usb_audio_input_terminal *it_v1;
303e2524b2eSHans Petter Selasky 		const struct usb_audio_output_terminal *ot_v1;
304e2524b2eSHans Petter Selasky 		const struct usb_audio_mixer_unit_0 *mu_v1;
305e2524b2eSHans Petter Selasky 		const struct usb_audio_selector_unit *su_v1;
306e2524b2eSHans Petter Selasky 		const struct usb_audio_feature_unit *fu_v1;
307e2524b2eSHans Petter Selasky 		const struct usb_audio_processing_unit_0 *pu_v1;
308e2524b2eSHans Petter Selasky 		const struct usb_audio_extension_unit_0 *eu_v1;
309e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_source_unit *csrc_v2;
310e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_selector_unit_0 *csel_v2;
311e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_multiplier_unit *cmul_v2;
312e2524b2eSHans Petter Selasky 		const struct usb_audio20_input_terminal *it_v2;
313e2524b2eSHans Petter Selasky 		const struct usb_audio20_output_terminal *ot_v2;
314e2524b2eSHans Petter Selasky 		const struct usb_audio20_mixer_unit_0 *mu_v2;
315e2524b2eSHans Petter Selasky 		const struct usb_audio20_selector_unit *su_v2;
316e2524b2eSHans Petter Selasky 		const struct usb_audio20_feature_unit *fu_v2;
317e2524b2eSHans Petter Selasky 		const struct usb_audio20_sample_rate_unit *ru_v2;
318e2524b2eSHans Petter Selasky 		const struct usb_audio20_processing_unit_0 *pu_v2;
319e2524b2eSHans Petter Selasky 		const struct usb_audio20_extension_unit_0 *eu_v2;
320e2524b2eSHans Petter Selasky 		const struct usb_audio20_effect_unit *ef_v2;
3213a3f90c6SAndrew Thompson 	}	u;
3223a3f90c6SAndrew Thompson 	struct uaudio_search_result usr;
3233a3f90c6SAndrew Thompson 	struct uaudio_terminal_node *root;
3243a3f90c6SAndrew Thompson };
3253a3f90c6SAndrew Thompson 
3263a3f90c6SAndrew Thompson struct uaudio_format {
3273a3f90c6SAndrew Thompson 	uint16_t wFormat;
3283a3f90c6SAndrew Thompson 	uint8_t	bPrecision;
3293a3f90c6SAndrew Thompson 	uint32_t freebsd_fmt;
3303a3f90c6SAndrew Thompson 	const char *description;
3313a3f90c6SAndrew Thompson };
3323a3f90c6SAndrew Thompson 
333e2524b2eSHans Petter Selasky static const struct uaudio_format uaudio10_formats[] = {
3343a3f90c6SAndrew Thompson 
3353a3f90c6SAndrew Thompson 	{UA_FMT_PCM8, 8, AFMT_U8, "8-bit U-LE PCM"},
3363a3f90c6SAndrew Thompson 	{UA_FMT_PCM8, 16, AFMT_U16_LE, "16-bit U-LE PCM"},
3373a3f90c6SAndrew Thompson 	{UA_FMT_PCM8, 24, AFMT_U24_LE, "24-bit U-LE PCM"},
3383a3f90c6SAndrew Thompson 	{UA_FMT_PCM8, 32, AFMT_U32_LE, "32-bit U-LE PCM"},
3393a3f90c6SAndrew Thompson 
3403a3f90c6SAndrew Thompson 	{UA_FMT_PCM, 8, AFMT_S8, "8-bit S-LE PCM"},
3413a3f90c6SAndrew Thompson 	{UA_FMT_PCM, 16, AFMT_S16_LE, "16-bit S-LE PCM"},
3423a3f90c6SAndrew Thompson 	{UA_FMT_PCM, 24, AFMT_S24_LE, "24-bit S-LE PCM"},
3433a3f90c6SAndrew Thompson 	{UA_FMT_PCM, 32, AFMT_S32_LE, "32-bit S-LE PCM"},
3443a3f90c6SAndrew Thompson 
3453a3f90c6SAndrew Thompson 	{UA_FMT_ALAW, 8, AFMT_A_LAW, "8-bit A-Law"},
3463a3f90c6SAndrew Thompson 	{UA_FMT_MULAW, 8, AFMT_MU_LAW, "8-bit mu-Law"},
3473a3f90c6SAndrew Thompson 
3483a3f90c6SAndrew Thompson 	{0, 0, 0, NULL}
3493a3f90c6SAndrew Thompson };
3503a3f90c6SAndrew Thompson 
351e2524b2eSHans Petter Selasky static const struct uaudio_format uaudio20_formats[] = {
352e2524b2eSHans Petter Selasky 
353e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM, 8, AFMT_S8, "8-bit S-LE PCM"},
354e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM, 16, AFMT_S16_LE, "16-bit S-LE PCM"},
355e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM, 24, AFMT_S24_LE, "24-bit S-LE PCM"},
356e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM, 32, AFMT_S32_LE, "32-bit S-LE PCM"},
357e2524b2eSHans Petter Selasky 
358e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM8, 8, AFMT_U8, "8-bit U-LE PCM"},
359e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM8, 16, AFMT_U16_LE, "16-bit U-LE PCM"},
360e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM8, 24, AFMT_U24_LE, "24-bit U-LE PCM"},
361e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM8, 32, AFMT_U32_LE, "32-bit U-LE PCM"},
362e2524b2eSHans Petter Selasky 
363e2524b2eSHans Petter Selasky 	{UA20_FMT_ALAW, 8, AFMT_A_LAW, "8-bit A-Law"},
364e2524b2eSHans Petter Selasky 	{UA20_FMT_MULAW, 8, AFMT_MU_LAW, "8-bit mu-Law"},
365e2524b2eSHans Petter Selasky 
366e2524b2eSHans Petter Selasky 	{0, 0, 0, NULL}
367e2524b2eSHans Petter Selasky };
368e2524b2eSHans Petter Selasky 
3693a3f90c6SAndrew Thompson #define	UAC_OUTPUT	0
3703a3f90c6SAndrew Thompson #define	UAC_INPUT	1
3713a3f90c6SAndrew Thompson #define	UAC_EQUAL	2
3723a3f90c6SAndrew Thompson #define	UAC_RECORD	3
3733a3f90c6SAndrew Thompson #define	UAC_NCLASSES	4
3743a3f90c6SAndrew Thompson 
375b850ecc1SAndrew Thompson #ifdef USB_DEBUG
3763a3f90c6SAndrew Thompson static const char *uac_names[] = {
3773a3f90c6SAndrew Thompson 	"outputs", "inputs", "equalization", "record"
3783a3f90c6SAndrew Thompson };
3793a3f90c6SAndrew Thompson 
3803a3f90c6SAndrew Thompson #endif
3813a3f90c6SAndrew Thompson 
3823a3f90c6SAndrew Thompson /* prototypes */
3833a3f90c6SAndrew Thompson 
3843a3f90c6SAndrew Thompson static device_probe_t uaudio_probe;
3853a3f90c6SAndrew Thompson static device_attach_t uaudio_attach;
3863a3f90c6SAndrew Thompson static device_detach_t uaudio_detach;
3873a3f90c6SAndrew Thompson 
388e0a69b51SAndrew Thompson static usb_callback_t uaudio_chan_play_callback;
389e0a69b51SAndrew Thompson static usb_callback_t uaudio_chan_record_callback;
390e0a69b51SAndrew Thompson static usb_callback_t uaudio_mixer_write_cfg_callback;
391e0a69b51SAndrew Thompson static usb_callback_t umidi_bulk_read_callback;
392e0a69b51SAndrew Thompson static usb_callback_t umidi_bulk_write_callback;
3933a3f90c6SAndrew Thompson 
394e2524b2eSHans Petter Selasky /* ==== USB audio v1.0 ==== */
395e2524b2eSHans Petter Selasky 
3963a3f90c6SAndrew Thompson static void	uaudio_mixer_add_mixer(struct uaudio_softc *,
3973a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
3983a3f90c6SAndrew Thompson static void	uaudio_mixer_add_selector(struct uaudio_softc *,
3993a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
4003a3f90c6SAndrew Thompson static uint32_t	uaudio_mixer_feature_get_bmaControls(
4014c21be9bSRebecca Cran 		    const struct usb_audio_feature_unit *, uint8_t);
4023a3f90c6SAndrew Thompson static void	uaudio_mixer_add_feature(struct uaudio_softc *,
4033a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
4043a3f90c6SAndrew Thompson static void	uaudio_mixer_add_processing_updown(struct uaudio_softc *,
4053a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
4063a3f90c6SAndrew Thompson static void	uaudio_mixer_add_processing(struct uaudio_softc *,
4073a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
4083a3f90c6SAndrew Thompson static void	uaudio_mixer_add_extension(struct uaudio_softc *,
4093a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
4104c21be9bSRebecca Cran static struct	usb_audio_cluster uaudio_mixer_get_cluster(uint8_t,
4113a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *);
4123a3f90c6SAndrew Thompson static uint16_t	uaudio_mixer_determine_class(const struct uaudio_terminal_node *,
4133a3f90c6SAndrew Thompson 		    struct uaudio_mixer_node *);
4143a3f90c6SAndrew Thompson static uint16_t	uaudio_mixer_feature_name(const struct uaudio_terminal_node *,
4153a3f90c6SAndrew Thompson 		    struct uaudio_mixer_node *);
416e2524b2eSHans Petter Selasky static void	uaudio_mixer_find_inputs_sub(struct uaudio_terminal_node *,
417e2524b2eSHans Petter Selasky 		    const uint8_t *, uint8_t, struct uaudio_search_result *);
418e2524b2eSHans Petter Selasky static const void *uaudio_mixer_verify_desc(const void *, uint32_t);
419e2524b2eSHans Petter Selasky static usb_error_t uaudio_set_speed(struct usb_device *, uint8_t, uint32_t);
420e2524b2eSHans Petter Selasky static int	uaudio_mixer_get(struct usb_device *, uint16_t, uint8_t,
421e2524b2eSHans Petter Selasky 		    struct uaudio_mixer_node *);
422e2524b2eSHans Petter Selasky 
423e2524b2eSHans Petter Selasky /* ==== USB audio v2.0 ==== */
424e2524b2eSHans Petter Selasky 
425e2524b2eSHans Petter Selasky static void	uaudio20_mixer_add_mixer(struct uaudio_softc *,
426e2524b2eSHans Petter Selasky 		    const struct uaudio_terminal_node *, int);
427e2524b2eSHans Petter Selasky static void	uaudio20_mixer_add_selector(struct uaudio_softc *,
428e2524b2eSHans Petter Selasky 		    const struct uaudio_terminal_node *, int);
429e2524b2eSHans Petter Selasky static void	uaudio20_mixer_add_feature(struct uaudio_softc *,
430e2524b2eSHans Petter Selasky 		    const struct uaudio_terminal_node *, int);
431e2524b2eSHans Petter Selasky static struct	usb_audio20_cluster uaudio20_mixer_get_cluster(uint8_t,
432e2524b2eSHans Petter Selasky 		    const struct uaudio_terminal_node *);
433e2524b2eSHans Petter Selasky static uint16_t	uaudio20_mixer_determine_class(const struct uaudio_terminal_node *,
434e2524b2eSHans Petter Selasky 		    struct uaudio_mixer_node *);
435e2524b2eSHans Petter Selasky static uint16_t	uaudio20_mixer_feature_name(const struct uaudio_terminal_node *,
436e2524b2eSHans Petter Selasky 		    struct uaudio_mixer_node *);
437e2524b2eSHans Petter Selasky static void	uaudio20_mixer_find_inputs_sub(struct uaudio_terminal_node *,
438e2524b2eSHans Petter Selasky 		    const uint8_t *, uint8_t, struct uaudio_search_result *);
439e2524b2eSHans Petter Selasky static const void *uaudio20_mixer_verify_desc(const void *, uint32_t);
440e2524b2eSHans Petter Selasky static usb_error_t uaudio20_set_speed(struct usb_device *, uint8_t,
441e2524b2eSHans Petter Selasky 		    uint8_t, uint32_t);
442e2524b2eSHans Petter Selasky 
443e2524b2eSHans Petter Selasky /* USB audio v1.0 and v2.0 */
444e2524b2eSHans Petter Selasky 
445e2524b2eSHans Petter Selasky static void	uaudio_chan_fill_info_sub(struct uaudio_softc *,
446e2524b2eSHans Petter Selasky 		    struct usb_device *, uint32_t, uint8_t, uint8_t);
447e2524b2eSHans Petter Selasky static void	uaudio_chan_fill_info(struct uaudio_softc *,
448e2524b2eSHans Petter Selasky 		    struct usb_device *);
449e2524b2eSHans Petter Selasky static void	uaudio_mixer_add_ctl_sub(struct uaudio_softc *,
450e2524b2eSHans Petter Selasky 		    struct uaudio_mixer_node *);
451e2524b2eSHans Petter Selasky static void	uaudio_mixer_add_ctl(struct uaudio_softc *,
452e2524b2eSHans Petter Selasky 		    struct uaudio_mixer_node *);
453e2524b2eSHans Petter Selasky static void	uaudio_mixer_fill_info(struct uaudio_softc *,
454e2524b2eSHans Petter Selasky 		    struct usb_device *, void *);
455e2524b2eSHans Petter Selasky static void	uaudio_mixer_ctl_set(struct uaudio_softc *,
456e2524b2eSHans Petter Selasky 		    struct uaudio_mixer_node *, uint8_t, int32_t val);
457e2524b2eSHans Petter Selasky static int	uaudio_mixer_signext(uint8_t, int);
458e2524b2eSHans Petter Selasky static int	uaudio_mixer_bsd2value(struct uaudio_mixer_node *, int32_t val);
459e2524b2eSHans Petter Selasky static void	uaudio_mixer_init(struct uaudio_softc *);
4603a3f90c6SAndrew Thompson static const struct uaudio_terminal_node *uaudio_mixer_get_input(
4613a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, uint8_t);
4623a3f90c6SAndrew Thompson static const struct uaudio_terminal_node *uaudio_mixer_get_output(
4633a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, uint8_t);
4643a3f90c6SAndrew Thompson static void	uaudio_mixer_find_outputs_sub(struct uaudio_terminal_node *,
4653a3f90c6SAndrew Thompson 		    uint8_t, uint8_t, struct uaudio_search_result *);
4663a3f90c6SAndrew Thompson static uint8_t	umidi_convert_to_usb(struct umidi_sub_chan *, uint8_t, uint8_t);
467760bc48eSAndrew Thompson static struct	umidi_sub_chan *umidi_sub_by_fifo(struct usb_fifo *);
468760bc48eSAndrew Thompson static void	umidi_start_read(struct usb_fifo *);
469760bc48eSAndrew Thompson static void	umidi_stop_read(struct usb_fifo *);
470760bc48eSAndrew Thompson static void	umidi_start_write(struct usb_fifo *);
471760bc48eSAndrew Thompson static void	umidi_stop_write(struct usb_fifo *);
472760bc48eSAndrew Thompson static int	umidi_open(struct usb_fifo *, int);
473760bc48eSAndrew Thompson static int	umidi_ioctl(struct usb_fifo *, u_long cmd, void *, int);
474760bc48eSAndrew Thompson static void	umidi_close(struct usb_fifo *, int);
4753a3f90c6SAndrew Thompson static void	umidi_init(device_t dev);
47625b74dabSHans Petter Selasky static int	umidi_probe(device_t dev);
47725b74dabSHans Petter Selasky static int	umidi_detach(device_t dev);
4783a3f90c6SAndrew Thompson 
479b850ecc1SAndrew Thompson #ifdef USB_DEBUG
4803a3f90c6SAndrew Thompson static void	uaudio_chan_dump_ep_desc(
4814c21be9bSRebecca Cran 		    const usb_endpoint_descriptor_audio_t *);
4823a3f90c6SAndrew Thompson #endif
4833a3f90c6SAndrew Thompson 
484760bc48eSAndrew Thompson static const struct usb_config
4853a3f90c6SAndrew Thompson 	uaudio_cfg_record[UAUDIO_NCHANBUFS] = {
4863a3f90c6SAndrew Thompson 	[0] = {
4873a3f90c6SAndrew Thompson 		.type = UE_ISOCHRONOUS,
4883a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
4893a3f90c6SAndrew Thompson 		.direction = UE_DIR_IN,
4904eae601eSAndrew Thompson 		.bufsize = 0,	/* use "wMaxPacketSize * frames" */
491b029f6bbSAndrew Thompson 		.frames = UAUDIO_NFRAMES,
4924eae601eSAndrew Thompson 		.flags = {.short_xfer_ok = 1,},
4934eae601eSAndrew Thompson 		.callback = &uaudio_chan_record_callback,
4943a3f90c6SAndrew Thompson 	},
4953a3f90c6SAndrew Thompson 
4963a3f90c6SAndrew Thompson 	[1] = {
4973a3f90c6SAndrew Thompson 		.type = UE_ISOCHRONOUS,
4983a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
4993a3f90c6SAndrew Thompson 		.direction = UE_DIR_IN,
5004eae601eSAndrew Thompson 		.bufsize = 0,	/* use "wMaxPacketSize * frames" */
501b029f6bbSAndrew Thompson 		.frames = UAUDIO_NFRAMES,
5024eae601eSAndrew Thompson 		.flags = {.short_xfer_ok = 1,},
5034eae601eSAndrew Thompson 		.callback = &uaudio_chan_record_callback,
5043a3f90c6SAndrew Thompson 	},
5053a3f90c6SAndrew Thompson };
5063a3f90c6SAndrew Thompson 
507760bc48eSAndrew Thompson static const struct usb_config
5083a3f90c6SAndrew Thompson 	uaudio_cfg_play[UAUDIO_NCHANBUFS] = {
5093a3f90c6SAndrew Thompson 	[0] = {
5103a3f90c6SAndrew Thompson 		.type = UE_ISOCHRONOUS,
5113a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
5123a3f90c6SAndrew Thompson 		.direction = UE_DIR_OUT,
5134eae601eSAndrew Thompson 		.bufsize = 0,	/* use "wMaxPacketSize * frames" */
514b029f6bbSAndrew Thompson 		.frames = UAUDIO_NFRAMES,
5154eae601eSAndrew Thompson 		.flags = {.short_xfer_ok = 1,},
5164eae601eSAndrew Thompson 		.callback = &uaudio_chan_play_callback,
5173a3f90c6SAndrew Thompson 	},
5183a3f90c6SAndrew Thompson 
5193a3f90c6SAndrew Thompson 	[1] = {
5203a3f90c6SAndrew Thompson 		.type = UE_ISOCHRONOUS,
5213a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
5223a3f90c6SAndrew Thompson 		.direction = UE_DIR_OUT,
5234eae601eSAndrew Thompson 		.bufsize = 0,	/* use "wMaxPacketSize * frames" */
524b029f6bbSAndrew Thompson 		.frames = UAUDIO_NFRAMES,
5254eae601eSAndrew Thompson 		.flags = {.short_xfer_ok = 1,},
5264eae601eSAndrew Thompson 		.callback = &uaudio_chan_play_callback,
5273a3f90c6SAndrew Thompson 	},
5283a3f90c6SAndrew Thompson };
5293a3f90c6SAndrew Thompson 
530760bc48eSAndrew Thompson static const struct usb_config
5313a3f90c6SAndrew Thompson 	uaudio_mixer_config[1] = {
5323a3f90c6SAndrew Thompson 	[0] = {
5333a3f90c6SAndrew Thompson 		.type = UE_CONTROL,
5343a3f90c6SAndrew Thompson 		.endpoint = 0x00,	/* Control pipe */
5353a3f90c6SAndrew Thompson 		.direction = UE_DIR_ANY,
536760bc48eSAndrew Thompson 		.bufsize = (sizeof(struct usb_device_request) + 4),
5374eae601eSAndrew Thompson 		.callback = &uaudio_mixer_write_cfg_callback,
5384eae601eSAndrew Thompson 		.timeout = 1000,	/* 1 second */
5393a3f90c6SAndrew Thompson 	},
5403a3f90c6SAndrew Thompson };
5413a3f90c6SAndrew Thompson 
5423a3f90c6SAndrew Thompson static const
5433a3f90c6SAndrew Thompson uint8_t	umidi_cmd_to_len[16] = {
5443a3f90c6SAndrew Thompson 	[0x0] = 0,			/* reserved */
5453a3f90c6SAndrew Thompson 	[0x1] = 0,			/* reserved */
5463a3f90c6SAndrew Thompson 	[0x2] = 2,			/* bytes */
5473a3f90c6SAndrew Thompson 	[0x3] = 3,			/* bytes */
5483a3f90c6SAndrew Thompson 	[0x4] = 3,			/* bytes */
5493a3f90c6SAndrew Thompson 	[0x5] = 1,			/* bytes */
5503a3f90c6SAndrew Thompson 	[0x6] = 2,			/* bytes */
5513a3f90c6SAndrew Thompson 	[0x7] = 3,			/* bytes */
5523a3f90c6SAndrew Thompson 	[0x8] = 3,			/* bytes */
5533a3f90c6SAndrew Thompson 	[0x9] = 3,			/* bytes */
5543a3f90c6SAndrew Thompson 	[0xA] = 3,			/* bytes */
5553a3f90c6SAndrew Thompson 	[0xB] = 3,			/* bytes */
5563a3f90c6SAndrew Thompson 	[0xC] = 2,			/* bytes */
5573a3f90c6SAndrew Thompson 	[0xD] = 2,			/* bytes */
5583a3f90c6SAndrew Thompson 	[0xE] = 3,			/* bytes */
5593a3f90c6SAndrew Thompson 	[0xF] = 1,			/* bytes */
5603a3f90c6SAndrew Thompson };
5613a3f90c6SAndrew Thompson 
562760bc48eSAndrew Thompson static const struct usb_config
5633a3f90c6SAndrew Thompson 	umidi_config[UMIDI_N_TRANSFER] = {
5646f068a43SHans Petter Selasky 	[UMIDI_TX_TRANSFER] = {
5653a3f90c6SAndrew Thompson 		.type = UE_BULK,
5663a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
5673a3f90c6SAndrew Thompson 		.direction = UE_DIR_OUT,
568910f1dcfSHans Petter Selasky 		.bufsize = UMIDI_TX_BUFFER,
5694eae601eSAndrew Thompson 		.callback = &umidi_bulk_write_callback,
5703a3f90c6SAndrew Thompson 	},
5713a3f90c6SAndrew Thompson 
5726f068a43SHans Petter Selasky 	[UMIDI_RX_TRANSFER] = {
5733a3f90c6SAndrew Thompson 		.type = UE_BULK,
5743a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
5753a3f90c6SAndrew Thompson 		.direction = UE_DIR_IN,
5763851442cSAndrew Thompson 		.bufsize = 4,	/* bytes */
577910f1dcfSHans Petter Selasky 		.flags = {.short_xfer_ok = 1,.proxy_buffer = 1,},
5784eae601eSAndrew Thompson 		.callback = &umidi_bulk_read_callback,
5793a3f90c6SAndrew Thompson 	},
5803a3f90c6SAndrew Thompson };
5813a3f90c6SAndrew Thompson 
5823a3f90c6SAndrew Thompson static devclass_t uaudio_devclass;
5833a3f90c6SAndrew Thompson 
5843a3f90c6SAndrew Thompson static device_method_t uaudio_methods[] = {
5853a3f90c6SAndrew Thompson 	DEVMETHOD(device_probe, uaudio_probe),
5863a3f90c6SAndrew Thompson 	DEVMETHOD(device_attach, uaudio_attach),
5873a3f90c6SAndrew Thompson 	DEVMETHOD(device_detach, uaudio_detach),
5883a3f90c6SAndrew Thompson 	DEVMETHOD(device_suspend, bus_generic_suspend),
5893a3f90c6SAndrew Thompson 	DEVMETHOD(device_resume, bus_generic_resume),
5903a3f90c6SAndrew Thompson 	DEVMETHOD(device_shutdown, bus_generic_shutdown),
5914b7ec270SMarius Strobl 
5924b7ec270SMarius Strobl 	DEVMETHOD_END
5933a3f90c6SAndrew Thompson };
5943a3f90c6SAndrew Thompson 
5953a3f90c6SAndrew Thompson static driver_t uaudio_driver = {
5963a3f90c6SAndrew Thompson 	.name = "uaudio",
5973a3f90c6SAndrew Thompson 	.methods = uaudio_methods,
5983a3f90c6SAndrew Thompson 	.size = sizeof(struct uaudio_softc),
5993a3f90c6SAndrew Thompson };
6003a3f90c6SAndrew Thompson 
601f1a16106SHans Petter Selasky static const STRUCT_USB_HOST_ID __used uaudio_devs[] = {
602f1a16106SHans Petter Selasky 	/* Generic USB audio class match */
603f1a16106SHans Petter Selasky 	{USB_IFACE_CLASS(UICLASS_AUDIO),
604f1a16106SHans Petter Selasky 	 USB_IFACE_SUBCLASS(UISUBCLASS_AUDIOCONTROL),},
605f1a16106SHans Petter Selasky 	/* Generic USB MIDI class match */
606f1a16106SHans Petter Selasky 	{USB_IFACE_CLASS(UICLASS_AUDIO),
607f1a16106SHans Petter Selasky 	 USB_IFACE_SUBCLASS(UISUBCLASS_MIDISTREAM),},
608f1a16106SHans Petter Selasky };
609f1a16106SHans Petter Selasky 
6103a3f90c6SAndrew Thompson static int
6113a3f90c6SAndrew Thompson uaudio_probe(device_t dev)
6123a3f90c6SAndrew Thompson {
613760bc48eSAndrew Thompson 	struct usb_attach_arg *uaa = device_get_ivars(dev);
6143a3f90c6SAndrew Thompson 
615fadc970bSAndrew Thompson 	if (uaa->usb_mode != USB_MODE_HOST)
6163a3f90c6SAndrew Thompson 		return (ENXIO);
6173a3f90c6SAndrew Thompson 
61825b74dabSHans Petter Selasky 	/* lookup non-standard device */
6193a3f90c6SAndrew Thompson 
62025b74dabSHans Petter Selasky 	if (uaa->info.bInterfaceClass != UICLASS_AUDIO) {
62125b74dabSHans Petter Selasky 		if (usb_test_quirk(uaa, UQ_AU_VENDOR_CLASS) == 0)
62225b74dabSHans Petter Selasky 			return (ENXIO);
62325b74dabSHans Petter Selasky 	}
62425b74dabSHans Petter Selasky 
62525b74dabSHans Petter Selasky 	/* check for AUDIO control interface */
62625b74dabSHans Petter Selasky 
62725b74dabSHans Petter Selasky 	if (uaa->info.bInterfaceSubClass == UISUBCLASS_AUDIOCONTROL) {
628a593f6b8SAndrew Thompson 		if (usb_test_quirk(uaa, UQ_BAD_AUDIO))
6293a3f90c6SAndrew Thompson 			return (ENXIO);
6303a3f90c6SAndrew Thompson 		else
631cd10bffaSAndriy Gapon 			return (BUS_PROBE_GENERIC);
6323a3f90c6SAndrew Thompson 	}
633dc694251SAndrew Thompson 
634dc694251SAndrew Thompson 	/* check for MIDI stream */
635dc694251SAndrew Thompson 
63625b74dabSHans Petter Selasky 	if (uaa->info.bInterfaceSubClass == UISUBCLASS_MIDISTREAM) {
63725b74dabSHans Petter Selasky 		if (usb_test_quirk(uaa, UQ_BAD_MIDI))
63825b74dabSHans Petter Selasky 			return (ENXIO);
63925b74dabSHans Petter Selasky 		else
640cd10bffaSAndriy Gapon 			return (BUS_PROBE_GENERIC);
641dc694251SAndrew Thompson 	}
6423a3f90c6SAndrew Thompson 	return (ENXIO);
6433a3f90c6SAndrew Thompson }
6443a3f90c6SAndrew Thompson 
6453a3f90c6SAndrew Thompson static int
6463a3f90c6SAndrew Thompson uaudio_attach(device_t dev)
6473a3f90c6SAndrew Thompson {
648760bc48eSAndrew Thompson 	struct usb_attach_arg *uaa = device_get_ivars(dev);
6493a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(dev);
650760bc48eSAndrew Thompson 	struct usb_interface_descriptor *id;
6513a3f90c6SAndrew Thompson 	device_t child;
6523a3f90c6SAndrew Thompson 
6533a3f90c6SAndrew Thompson 	sc->sc_play_chan.priv_sc = sc;
6543a3f90c6SAndrew Thompson 	sc->sc_rec_chan.priv_sc = sc;
6553a3f90c6SAndrew Thompson 	sc->sc_udev = uaa->device;
656b029f6bbSAndrew Thompson 	sc->sc_mixer_iface_index = uaa->info.bIfaceIndex;
657b029f6bbSAndrew Thompson 	sc->sc_mixer_iface_no = uaa->info.bIfaceNum;
6583a3f90c6SAndrew Thompson 
659a593f6b8SAndrew Thompson 	if (usb_test_quirk(uaa, UQ_AUDIO_SWAP_LR))
6603a3f90c6SAndrew Thompson 		sc->sc_uq_audio_swap_lr = 1;
6613a3f90c6SAndrew Thompson 
662a593f6b8SAndrew Thompson 	if (usb_test_quirk(uaa, UQ_AU_INP_ASYNC))
6633a3f90c6SAndrew Thompson 		sc->sc_uq_au_inp_async = 1;
6643a3f90c6SAndrew Thompson 
665a593f6b8SAndrew Thompson 	if (usb_test_quirk(uaa, UQ_AU_NO_XU))
6663a3f90c6SAndrew Thompson 		sc->sc_uq_au_no_xu = 1;
6673a3f90c6SAndrew Thompson 
668a593f6b8SAndrew Thompson 	if (usb_test_quirk(uaa, UQ_BAD_ADC))
6693a3f90c6SAndrew Thompson 		sc->sc_uq_bad_adc = 1;
6703a3f90c6SAndrew Thompson 
67125b74dabSHans Petter Selasky 	if (usb_test_quirk(uaa, UQ_AU_VENDOR_CLASS))
67225b74dabSHans Petter Selasky 		sc->sc_uq_au_vendor_class = 1;
67325b74dabSHans Petter Selasky 
6743a3f90c6SAndrew Thompson 	umidi_init(dev);
6753a3f90c6SAndrew Thompson 
676a593f6b8SAndrew Thompson 	device_set_usb_desc(dev);
6773a3f90c6SAndrew Thompson 
678a593f6b8SAndrew Thompson 	id = usbd_get_interface_descriptor(uaa->iface);
6793a3f90c6SAndrew Thompson 
680e2524b2eSHans Petter Selasky 	/* must fill mixer info before channel info */
6813a3f90c6SAndrew Thompson 	uaudio_mixer_fill_info(sc, uaa->device, id);
6823a3f90c6SAndrew Thompson 
683e2524b2eSHans Petter Selasky 	/* fill channel info */
684e2524b2eSHans Petter Selasky 	uaudio_chan_fill_info(sc, uaa->device);
685e2524b2eSHans Petter Selasky 
6863a3f90c6SAndrew Thompson 	DPRINTF("audio rev %d.%02x\n",
6873a3f90c6SAndrew Thompson 	    sc->sc_audio_rev >> 8,
6883a3f90c6SAndrew Thompson 	    sc->sc_audio_rev & 0xff);
6893a3f90c6SAndrew Thompson 
6903a3f90c6SAndrew Thompson 	DPRINTF("%d mixer controls\n",
6913a3f90c6SAndrew Thompson 	    sc->sc_mixer_count);
6923a3f90c6SAndrew Thompson 
6933a3f90c6SAndrew Thompson 	if (sc->sc_play_chan.valid) {
6949b5da816SHans Petter Selasky 		device_printf(dev, "Play: %d Hz, %d ch, %s format.\n",
6953a3f90c6SAndrew Thompson 		    sc->sc_play_chan.sample_rate,
696e2524b2eSHans Petter Selasky 		    sc->sc_play_chan.channels,
6973a3f90c6SAndrew Thompson 		    sc->sc_play_chan.p_fmt->description);
6983a3f90c6SAndrew Thompson 	} else {
6999b5da816SHans Petter Selasky 		device_printf(dev, "No playback.\n");
7003a3f90c6SAndrew Thompson 	}
7013a3f90c6SAndrew Thompson 
7023a3f90c6SAndrew Thompson 	if (sc->sc_rec_chan.valid) {
7039b5da816SHans Petter Selasky 		device_printf(dev, "Record: %d Hz, %d ch, %s format.\n",
7043a3f90c6SAndrew Thompson 		    sc->sc_rec_chan.sample_rate,
705e2524b2eSHans Petter Selasky 		    sc->sc_play_chan.channels,
7063a3f90c6SAndrew Thompson 		    sc->sc_rec_chan.p_fmt->description);
7073a3f90c6SAndrew Thompson 	} else {
7089b5da816SHans Petter Selasky 		device_printf(dev, "No recording.\n");
7093a3f90c6SAndrew Thompson 	}
7103a3f90c6SAndrew Thompson 
7113a3f90c6SAndrew Thompson 	if (sc->sc_midi_chan.valid) {
7123a3f90c6SAndrew Thompson 
7133a3f90c6SAndrew Thompson 		if (umidi_probe(dev)) {
7143a3f90c6SAndrew Thompson 			goto detach;
7153a3f90c6SAndrew Thompson 		}
7169b5da816SHans Petter Selasky 		device_printf(dev, "MIDI sequencer.\n");
7173a3f90c6SAndrew Thompson 	} else {
7189b5da816SHans Petter Selasky 		device_printf(dev, "No midi sequencer.\n");
7193a3f90c6SAndrew Thompson 	}
7203a3f90c6SAndrew Thompson 
7213a3f90c6SAndrew Thompson 	DPRINTF("doing child attach\n");
7223a3f90c6SAndrew Thompson 
7233a3f90c6SAndrew Thompson 	/* attach the children */
7243a3f90c6SAndrew Thompson 
7253a3f90c6SAndrew Thompson 	sc->sc_sndcard_func.func = SCF_PCM;
7263a3f90c6SAndrew Thompson 
7279b5da816SHans Petter Selasky 	/*
7289b5da816SHans Petter Selasky 	 * Only attach a PCM device if we have a playback, recording
7299b5da816SHans Petter Selasky 	 * or mixer device present:
7309b5da816SHans Petter Selasky 	 */
7319b5da816SHans Petter Selasky 	if (sc->sc_play_chan.valid ||
7329b5da816SHans Petter Selasky 	    sc->sc_rec_chan.valid ||
7339b5da816SHans Petter Selasky 	    sc->sc_mix_info) {
7343a3f90c6SAndrew Thompson 		child = device_add_child(dev, "pcm", -1);
7353a3f90c6SAndrew Thompson 
7363a3f90c6SAndrew Thompson 		if (child == NULL) {
7373a3f90c6SAndrew Thompson 			DPRINTF("out of memory\n");
7383a3f90c6SAndrew Thompson 			goto detach;
7393a3f90c6SAndrew Thompson 		}
7403a3f90c6SAndrew Thompson 		device_set_ivars(child, &sc->sc_sndcard_func);
7419b5da816SHans Petter Selasky 	}
7423a3f90c6SAndrew Thompson 
7433a3f90c6SAndrew Thompson 	if (bus_generic_attach(dev)) {
7443a3f90c6SAndrew Thompson 		DPRINTF("child attach failed\n");
7453a3f90c6SAndrew Thompson 		goto detach;
7463a3f90c6SAndrew Thompson 	}
7473a3f90c6SAndrew Thompson 	return (0);			/* success */
7483a3f90c6SAndrew Thompson 
7493a3f90c6SAndrew Thompson detach:
7503a3f90c6SAndrew Thompson 	uaudio_detach(dev);
7513a3f90c6SAndrew Thompson 	return (ENXIO);
7523a3f90c6SAndrew Thompson }
7533a3f90c6SAndrew Thompson 
7543a3f90c6SAndrew Thompson static void
7553a3f90c6SAndrew Thompson uaudio_pcm_setflags(device_t dev, uint32_t flags)
7563a3f90c6SAndrew Thompson {
7573a3f90c6SAndrew Thompson 	pcm_setflags(dev, pcm_getflags(dev) | flags);
7583a3f90c6SAndrew Thompson }
7593a3f90c6SAndrew Thompson 
7603a3f90c6SAndrew Thompson int
7613a3f90c6SAndrew Thompson uaudio_attach_sub(device_t dev, kobj_class_t mixer_class, kobj_class_t chan_class)
7623a3f90c6SAndrew Thompson {
7633a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(device_get_parent(dev));
7643a3f90c6SAndrew Thompson 	char status[SND_STATUSLEN];
7653a3f90c6SAndrew Thompson 
7663a3f90c6SAndrew Thompson 	uaudio_mixer_init(sc);
7673a3f90c6SAndrew Thompson 
7683a3f90c6SAndrew Thompson 	if (sc->sc_uq_audio_swap_lr) {
7693a3f90c6SAndrew Thompson 		DPRINTF("hardware has swapped left and right\n");
77090da2b28SAriff Abdullah 		/* uaudio_pcm_setflags(dev, SD_F_PSWAPLR); */
7713a3f90c6SAndrew Thompson 	}
7723a3f90c6SAndrew Thompson 	if (!(sc->sc_mix_info & SOUND_MASK_PCM)) {
7733a3f90c6SAndrew Thompson 
7743a3f90c6SAndrew Thompson 		DPRINTF("emulating master volume\n");
7753a3f90c6SAndrew Thompson 
7763a3f90c6SAndrew Thompson 		/*
7773a3f90c6SAndrew Thompson 		 * Emulate missing pcm mixer controller
7783a3f90c6SAndrew Thompson 		 * through FEEDER_VOLUME
7793a3f90c6SAndrew Thompson 		 */
7803a3f90c6SAndrew Thompson 		uaudio_pcm_setflags(dev, SD_F_SOFTPCMVOL);
7813a3f90c6SAndrew Thompson 	}
7823a3f90c6SAndrew Thompson 	if (mixer_init(dev, mixer_class, sc)) {
7833a3f90c6SAndrew Thompson 		goto detach;
7843a3f90c6SAndrew Thompson 	}
7853a3f90c6SAndrew Thompson 	sc->sc_mixer_init = 1;
7863a3f90c6SAndrew Thompson 
7873a3f90c6SAndrew Thompson 	snprintf(status, sizeof(status), "at ? %s", PCM_KLDSTRING(snd_uaudio));
7883a3f90c6SAndrew Thompson 
7893a3f90c6SAndrew Thompson 	if (pcm_register(dev, sc,
7903a3f90c6SAndrew Thompson 	    sc->sc_play_chan.valid ? 1 : 0,
7913a3f90c6SAndrew Thompson 	    sc->sc_rec_chan.valid ? 1 : 0)) {
7923a3f90c6SAndrew Thompson 		goto detach;
7933a3f90c6SAndrew Thompson 	}
79490da2b28SAriff Abdullah 
79590da2b28SAriff Abdullah 	uaudio_pcm_setflags(dev, SD_F_MPSAFE);
7963a3f90c6SAndrew Thompson 	sc->sc_pcm_registered = 1;
7973a3f90c6SAndrew Thompson 
7983a3f90c6SAndrew Thompson 	if (sc->sc_play_chan.valid) {
7993a3f90c6SAndrew Thompson 		pcm_addchan(dev, PCMDIR_PLAY, chan_class, sc);
8003a3f90c6SAndrew Thompson 	}
8013a3f90c6SAndrew Thompson 	if (sc->sc_rec_chan.valid) {
8023a3f90c6SAndrew Thompson 		pcm_addchan(dev, PCMDIR_REC, chan_class, sc);
8033a3f90c6SAndrew Thompson 	}
8043a3f90c6SAndrew Thompson 	pcm_setstatus(dev, status);
8053a3f90c6SAndrew Thompson 
8063a3f90c6SAndrew Thompson 	return (0);			/* success */
8073a3f90c6SAndrew Thompson 
8083a3f90c6SAndrew Thompson detach:
8093a3f90c6SAndrew Thompson 	uaudio_detach_sub(dev);
8103a3f90c6SAndrew Thompson 	return (ENXIO);
8113a3f90c6SAndrew Thompson }
8123a3f90c6SAndrew Thompson 
8133a3f90c6SAndrew Thompson int
8143a3f90c6SAndrew Thompson uaudio_detach_sub(device_t dev)
8153a3f90c6SAndrew Thompson {
8163a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(device_get_parent(dev));
8173a3f90c6SAndrew Thompson 	int error = 0;
8183a3f90c6SAndrew Thompson 
8193a3f90c6SAndrew Thompson repeat:
8203a3f90c6SAndrew Thompson 	if (sc->sc_pcm_registered) {
8213a3f90c6SAndrew Thompson 		error = pcm_unregister(dev);
8223a3f90c6SAndrew Thompson 	} else {
8233a3f90c6SAndrew Thompson 		if (sc->sc_mixer_init) {
8243a3f90c6SAndrew Thompson 			error = mixer_uninit(dev);
8253a3f90c6SAndrew Thompson 		}
8263a3f90c6SAndrew Thompson 	}
8273a3f90c6SAndrew Thompson 
8283a3f90c6SAndrew Thompson 	if (error) {
8293a3f90c6SAndrew Thompson 		device_printf(dev, "Waiting for sound application to exit!\n");
830a593f6b8SAndrew Thompson 		usb_pause_mtx(NULL, 2 * hz);
8313a3f90c6SAndrew Thompson 		goto repeat;		/* try again */
8323a3f90c6SAndrew Thompson 	}
8333a3f90c6SAndrew Thompson 	return (0);			/* success */
8343a3f90c6SAndrew Thompson }
8353a3f90c6SAndrew Thompson 
8363a3f90c6SAndrew Thompson static int
8373a3f90c6SAndrew Thompson uaudio_detach(device_t dev)
8383a3f90c6SAndrew Thompson {
8393a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(dev);
8403a3f90c6SAndrew Thompson 
841a6ed4937SHans Petter Selasky 	/*
842a6ed4937SHans Petter Selasky 	 * Stop USB transfers early so that any audio applications
843a6ed4937SHans Petter Selasky 	 * will time out and close opened /dev/dspX.Y device(s), if
844a6ed4937SHans Petter Selasky 	 * any.
845a6ed4937SHans Petter Selasky 	 */
84611b81cedSHans Petter Selasky 	if (sc->sc_play_chan.valid)
84711b81cedSHans Petter Selasky 		usbd_transfer_unsetup(sc->sc_play_chan.xfer, UAUDIO_NCHANBUFS);
84811b81cedSHans Petter Selasky 	if (sc->sc_rec_chan.valid)
84911b81cedSHans Petter Selasky 		usbd_transfer_unsetup(sc->sc_rec_chan.xfer, UAUDIO_NCHANBUFS);
850a6ed4937SHans Petter Selasky 
851a6ed4937SHans Petter Selasky 	if (bus_generic_detach(dev) != 0) {
8523a3f90c6SAndrew Thompson 		DPRINTF("detach failed!\n");
8533a3f90c6SAndrew Thompson 	}
8543a3f90c6SAndrew Thompson 	sbuf_delete(&sc->sc_sndstat);
8553a3f90c6SAndrew Thompson 	sc->sc_sndstat_valid = 0;
8563a3f90c6SAndrew Thompson 
8573a3f90c6SAndrew Thompson 	umidi_detach(dev);
8583a3f90c6SAndrew Thompson 
8593a3f90c6SAndrew Thompson 	return (0);
8603a3f90c6SAndrew Thompson }
8613a3f90c6SAndrew Thompson 
8623a3f90c6SAndrew Thompson /*========================================================================*
8633a3f90c6SAndrew Thompson  * AS - Audio Stream - routines
8643a3f90c6SAndrew Thompson  *========================================================================*/
8653a3f90c6SAndrew Thompson 
866b850ecc1SAndrew Thompson #ifdef USB_DEBUG
8673a3f90c6SAndrew Thompson static void
8684c21be9bSRebecca Cran uaudio_chan_dump_ep_desc(const usb_endpoint_descriptor_audio_t *ed)
8693a3f90c6SAndrew Thompson {
8703a3f90c6SAndrew Thompson 	if (ed) {
8713a3f90c6SAndrew Thompson 		DPRINTF("endpoint=%p bLength=%d bDescriptorType=%d \n"
8723a3f90c6SAndrew Thompson 		    "bEndpointAddress=%d bmAttributes=0x%x \n"
8733a3f90c6SAndrew Thompson 		    "wMaxPacketSize=%d bInterval=%d \n"
8743a3f90c6SAndrew Thompson 		    "bRefresh=%d bSynchAddress=%d\n",
8753a3f90c6SAndrew Thompson 		    ed, ed->bLength, ed->bDescriptorType,
8763a3f90c6SAndrew Thompson 		    ed->bEndpointAddress, ed->bmAttributes,
8773a3f90c6SAndrew Thompson 		    UGETW(ed->wMaxPacketSize), ed->bInterval,
878e3e05e50SAndrew Thompson 		    UEP_HAS_REFRESH(ed) ? ed->bRefresh : 0,
879e3e05e50SAndrew Thompson 		    UEP_HAS_SYNCADDR(ed) ? ed->bSynchAddress : 0);
8803a3f90c6SAndrew Thompson 	}
8813a3f90c6SAndrew Thompson }
8823a3f90c6SAndrew Thompson 
8833a3f90c6SAndrew Thompson #endif
8843a3f90c6SAndrew Thompson 
885f895cc0cSHans Petter Selasky /*
886f895cc0cSHans Petter Selasky  * The following is a workaround for broken no-name USB audio devices
887f895cc0cSHans Petter Selasky  * sold by dealextreme called "3D sound". The problem is that the
888f895cc0cSHans Petter Selasky  * manufacturer computed wMaxPacketSize is too small to hold the
889f895cc0cSHans Petter Selasky  * actual data sent. In other words the device sometimes sends more
890f895cc0cSHans Petter Selasky  * data than it actually reports it can send in a single isochronous
891f895cc0cSHans Petter Selasky  * packet.
892f895cc0cSHans Petter Selasky  */
893f895cc0cSHans Petter Selasky static void
894f895cc0cSHans Petter Selasky uaudio_record_fix_fs(usb_endpoint_descriptor_audio_t *ep,
895f895cc0cSHans Petter Selasky     uint32_t xps, uint32_t add)
896f895cc0cSHans Petter Selasky {
897f895cc0cSHans Petter Selasky 	uint32_t mps;
898f895cc0cSHans Petter Selasky 
899f895cc0cSHans Petter Selasky 	mps = UGETW(ep->wMaxPacketSize);
900f895cc0cSHans Petter Selasky 
901f895cc0cSHans Petter Selasky 	/*
902f895cc0cSHans Petter Selasky 	 * If the device indicates it can send more data than what the
903f895cc0cSHans Petter Selasky 	 * sample rate indicates, we apply the workaround.
904f895cc0cSHans Petter Selasky 	 */
905f895cc0cSHans Petter Selasky 	if (mps > xps) {
906f895cc0cSHans Petter Selasky 
907f895cc0cSHans Petter Selasky 		/* allow additional data */
908f895cc0cSHans Petter Selasky 		xps += add;
909f895cc0cSHans Petter Selasky 
910f895cc0cSHans Petter Selasky 		/* check against the maximum USB 1.x length */
911f895cc0cSHans Petter Selasky 		if (xps > 1023)
912f895cc0cSHans Petter Selasky 			xps = 1023;
913f895cc0cSHans Petter Selasky 
914f895cc0cSHans Petter Selasky 		/* check if we should do an update */
915f895cc0cSHans Petter Selasky 		if (mps < xps) {
916f895cc0cSHans Petter Selasky 			/* simply update the wMaxPacketSize field */
917f895cc0cSHans Petter Selasky 			USETW(ep->wMaxPacketSize, xps);
918f895cc0cSHans Petter Selasky 			DPRINTF("Workaround: Updated wMaxPacketSize "
919f895cc0cSHans Petter Selasky 			    "from %d to %d bytes.\n",
920f895cc0cSHans Petter Selasky 			    (int)mps, (int)xps);
921f895cc0cSHans Petter Selasky 		}
922f895cc0cSHans Petter Selasky 	}
923f895cc0cSHans Petter Selasky }
924f895cc0cSHans Petter Selasky 
925e2524b2eSHans Petter Selasky static usb_error_t
926e2524b2eSHans Petter Selasky uaudio20_check_rate(struct usb_device *udev, uint8_t iface_no,
927e2524b2eSHans Petter Selasky     uint8_t clockid, uint32_t rate)
928e2524b2eSHans Petter Selasky {
929e2524b2eSHans Petter Selasky 	struct usb_device_request req;
930e2524b2eSHans Petter Selasky 	usb_error_t error;
931e2524b2eSHans Petter Selasky 	uint8_t data[255];
932e2524b2eSHans Petter Selasky 	uint16_t actlen;
933e2524b2eSHans Petter Selasky 	uint16_t rates;
934e2524b2eSHans Petter Selasky 	uint16_t x;
935e2524b2eSHans Petter Selasky 
936e2524b2eSHans Petter Selasky 	DPRINTFN(6, "ifaceno=%d clockid=%d rate=%u\n",
937e2524b2eSHans Petter Selasky 	    iface_no, clockid, rate);
938e2524b2eSHans Petter Selasky 
939e2524b2eSHans Petter Selasky 	req.bmRequestType = UT_READ_CLASS_INTERFACE;
940e2524b2eSHans Petter Selasky 	req.bRequest = UA20_CS_RANGE;
941e2524b2eSHans Petter Selasky 	USETW2(req.wValue, UA20_CS_SAM_FREQ_CONTROL, 0);
942e2524b2eSHans Petter Selasky 	USETW2(req.wIndex, clockid, iface_no);
943e2524b2eSHans Petter Selasky 	USETW(req.wLength, 255);
944e2524b2eSHans Petter Selasky 
945e2524b2eSHans Petter Selasky         error = usbd_do_request_flags(udev, NULL, &req, data,
946e2524b2eSHans Petter Selasky 	    USB_SHORT_XFER_OK, &actlen, USB_DEFAULT_TIMEOUT);
947e2524b2eSHans Petter Selasky 
948e2524b2eSHans Petter Selasky 	if (error != 0 || actlen < 2)
949e2524b2eSHans Petter Selasky 		return (USB_ERR_INVAL);
950e2524b2eSHans Petter Selasky 
951e2524b2eSHans Petter Selasky 	rates = data[0] | (data[1] << 8);
952e2524b2eSHans Petter Selasky 	actlen = (actlen - 2) / 12;
953e2524b2eSHans Petter Selasky 
954e2524b2eSHans Petter Selasky 	if (rates > actlen) {
955e2524b2eSHans Petter Selasky 		DPRINTF("Too many rates\n");
956e2524b2eSHans Petter Selasky 		rates = actlen;
957e2524b2eSHans Petter Selasky 	}
958e2524b2eSHans Petter Selasky 
959e2524b2eSHans Petter Selasky 	for (x = 0; x != rates; x++) {
960e2524b2eSHans Petter Selasky 		uint32_t min = UGETDW(data + 2 + (12 * x));
961e2524b2eSHans Petter Selasky 		uint32_t max = UGETDW(data + 6 + (12 * x));
962e2524b2eSHans Petter Selasky 		uint32_t res = UGETDW(data + 10 + (12 * x));
963e2524b2eSHans Petter Selasky 
964e2524b2eSHans Petter Selasky 		if (res == 0) {
965e2524b2eSHans Petter Selasky 			DPRINTF("Zero residue\n");
966e2524b2eSHans Petter Selasky 			res = 1;
967e2524b2eSHans Petter Selasky 		}
968e2524b2eSHans Petter Selasky 
969e2524b2eSHans Petter Selasky 		if (min > max) {
970e2524b2eSHans Petter Selasky 			DPRINTF("Swapped max and min\n");
971e2524b2eSHans Petter Selasky 			uint32_t temp;
972e2524b2eSHans Petter Selasky 			temp = min;
973e2524b2eSHans Petter Selasky 			min = max;
974e2524b2eSHans Petter Selasky 			max = temp;
975e2524b2eSHans Petter Selasky 		}
976e2524b2eSHans Petter Selasky 
977e2524b2eSHans Petter Selasky 		if (rate >= min && rate <= max &&
978e2524b2eSHans Petter Selasky 		    (((rate - min) % res) == 0)) {
979e2524b2eSHans Petter Selasky 			return (0);
980e2524b2eSHans Petter Selasky 		}
981e2524b2eSHans Petter Selasky 	}
982e2524b2eSHans Petter Selasky 	return (USB_ERR_INVAL);
983e2524b2eSHans Petter Selasky }
984e2524b2eSHans Petter Selasky 
9853a3f90c6SAndrew Thompson static void
986760bc48eSAndrew Thompson uaudio_chan_fill_info_sub(struct uaudio_softc *sc, struct usb_device *udev,
987afbfddd9SAndrew Thompson     uint32_t rate, uint8_t channels, uint8_t bit_resolution)
9883a3f90c6SAndrew Thompson {
989760bc48eSAndrew Thompson 	struct usb_descriptor *desc = NULL;
990e2524b2eSHans Petter Selasky 	union uaudio_asid asid = { NULL };
991e2524b2eSHans Petter Selasky 	union uaudio_asf1d asf1d = { NULL };
992e2524b2eSHans Petter Selasky 	union uaudio_sed sed = { NULL };
993f895cc0cSHans Petter Selasky 	usb_endpoint_descriptor_audio_t *ed1 = NULL;
994e2524b2eSHans Petter Selasky 	const struct usb_audio_control_descriptor *acdp = NULL;
995a593f6b8SAndrew Thompson 	struct usb_config_descriptor *cd = usbd_get_config_descriptor(udev);
996760bc48eSAndrew Thompson 	struct usb_interface_descriptor *id;
997e2524b2eSHans Petter Selasky 	const struct uaudio_format *p_fmt = NULL;
9983a3f90c6SAndrew Thompson 	struct uaudio_chan *chan;
9993a3f90c6SAndrew Thompson 	uint16_t curidx = 0xFFFF;
10003a3f90c6SAndrew Thompson 	uint16_t lastidx = 0xFFFF;
10013a3f90c6SAndrew Thompson 	uint16_t alt_index = 0;
1002e2524b2eSHans Petter Selasky 	uint16_t audio_rev = 0;
1003e2524b2eSHans Petter Selasky 	uint16_t x;
10043a3f90c6SAndrew Thompson 	uint8_t ep_dir;
10053a3f90c6SAndrew Thompson 	uint8_t bChannels;
10063a3f90c6SAndrew Thompson 	uint8_t bBitResolution;
10073a3f90c6SAndrew Thompson 	uint8_t audio_if = 0;
100825b74dabSHans Petter Selasky 	uint8_t uma_if_class;
10093a3f90c6SAndrew Thompson 
1010a593f6b8SAndrew Thompson 	while ((desc = usb_desc_foreach(cd, desc))) {
10113a3f90c6SAndrew Thompson 
10123a3f90c6SAndrew Thompson 		if ((desc->bDescriptorType == UDESC_INTERFACE) &&
10133a3f90c6SAndrew Thompson 		    (desc->bLength >= sizeof(*id))) {
10143a3f90c6SAndrew Thompson 
10153a3f90c6SAndrew Thompson 			id = (void *)desc;
10163a3f90c6SAndrew Thompson 
10173a3f90c6SAndrew Thompson 			if (id->bInterfaceNumber != lastidx) {
10183a3f90c6SAndrew Thompson 				lastidx = id->bInterfaceNumber;
10193a3f90c6SAndrew Thompson 				curidx++;
10203a3f90c6SAndrew Thompson 				alt_index = 0;
10213a3f90c6SAndrew Thompson 
10223a3f90c6SAndrew Thompson 			} else {
10233a3f90c6SAndrew Thompson 				alt_index++;
10243a3f90c6SAndrew Thompson 			}
10253a3f90c6SAndrew Thompson 
102625b74dabSHans Petter Selasky 			uma_if_class =
102725b74dabSHans Petter Selasky 			    ((id->bInterfaceClass == UICLASS_AUDIO) ||
102825b74dabSHans Petter Selasky 			    ((id->bInterfaceClass == UICLASS_VENDOR) &&
102925b74dabSHans Petter Selasky 			    (sc->sc_uq_au_vendor_class != 0)));
103025b74dabSHans Petter Selasky 
103125b74dabSHans Petter Selasky 			if ((uma_if_class != 0) && (id->bInterfaceSubClass == UISUBCLASS_AUDIOSTREAM)) {
10323a3f90c6SAndrew Thompson 				audio_if = 1;
10333a3f90c6SAndrew Thompson 			} else {
10343a3f90c6SAndrew Thompson 				audio_if = 0;
10353a3f90c6SAndrew Thompson 			}
10363a3f90c6SAndrew Thompson 
103725b74dabSHans Petter Selasky 			if ((uma_if_class != 0) &&
10383a3f90c6SAndrew Thompson 			    (id->bInterfaceSubClass == UISUBCLASS_MIDISTREAM)) {
10393a3f90c6SAndrew Thompson 
10403a3f90c6SAndrew Thompson 				/*
10413a3f90c6SAndrew Thompson 				 * XXX could allow multiple MIDI interfaces
10423a3f90c6SAndrew Thompson 				 */
10433a3f90c6SAndrew Thompson 
10443a3f90c6SAndrew Thompson 				if ((sc->sc_midi_chan.valid == 0) &&
1045a593f6b8SAndrew Thompson 				    usbd_get_iface(udev, curidx)) {
10463a3f90c6SAndrew Thompson 					sc->sc_midi_chan.iface_index = curidx;
10473a3f90c6SAndrew Thompson 					sc->sc_midi_chan.iface_alt_index = alt_index;
10483a3f90c6SAndrew Thompson 					sc->sc_midi_chan.valid = 1;
10493a3f90c6SAndrew Thompson 				}
10503a3f90c6SAndrew Thompson 			}
1051e2524b2eSHans Petter Selasky 			asid.v1 = NULL;
1052e2524b2eSHans Petter Selasky 			asf1d.v1 = NULL;
10533a3f90c6SAndrew Thompson 			ed1 = NULL;
1054e2524b2eSHans Petter Selasky 			sed.v1 = NULL;
10553a3f90c6SAndrew Thompson 		}
1056e2524b2eSHans Petter Selasky 
10571234097eSHans Petter Selasky 		if (audio_if == 0) {
1058e2524b2eSHans Petter Selasky 			if ((acdp == NULL) &&
1059e2524b2eSHans Petter Selasky 			    (desc->bDescriptorType == UDESC_CS_INTERFACE) &&
1060e2524b2eSHans Petter Selasky 			    (desc->bDescriptorSubtype == UDESCSUB_AC_HEADER) &&
1061e2524b2eSHans Petter Selasky 			    (desc->bLength >= sizeof(*acdp))) {
1062e2524b2eSHans Petter Selasky 				acdp = (void *)desc;
1063e2524b2eSHans Petter Selasky 				audio_rev = UGETW(acdp->bcdADC);
1064e2524b2eSHans Petter Selasky 			}
1065e2524b2eSHans Petter Selasky 
10661234097eSHans Petter Selasky 			/*
10671234097eSHans Petter Selasky 			 * Don't collect any USB audio descriptors if
10681234097eSHans Petter Selasky 			 * this is not an USB audio stream interface.
10691234097eSHans Petter Selasky 			 */
10701234097eSHans Petter Selasky 			continue;
10711234097eSHans Petter Selasky 		}
10721234097eSHans Petter Selasky 
1073e2524b2eSHans Petter Selasky 		if ((acdp != NULL) &&
1074e2524b2eSHans Petter Selasky 		    (desc->bDescriptorType == UDESC_CS_INTERFACE) &&
1075e2524b2eSHans Petter Selasky 		    (desc->bDescriptorSubtype == AS_GENERAL) &&
1076e2524b2eSHans Petter Selasky 		    (asid.v1 == NULL)) {
1077e2524b2eSHans Petter Selasky 			if (audio_rev >= UAUDIO_VERSION_30) {
1078e2524b2eSHans Petter Selasky 				/* FALLTHROUGH */
1079e2524b2eSHans Petter Selasky 			} else if (audio_rev >= UAUDIO_VERSION_20) {
1080e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*asid.v2)) {
1081e2524b2eSHans Petter Selasky 					asid.v2 = (void *)desc;
1082e2524b2eSHans Petter Selasky 				}
1083e2524b2eSHans Petter Selasky 			} else {
1084e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*asid.v1)) {
1085e2524b2eSHans Petter Selasky 					asid.v1 = (void *)desc;
10863a3f90c6SAndrew Thompson 				}
10873a3f90c6SAndrew Thompson 			}
1088e2524b2eSHans Petter Selasky 		}
1089e2524b2eSHans Petter Selasky 		if ((acdp != NULL) &&
1090e2524b2eSHans Petter Selasky 		    (desc->bDescriptorType == UDESC_CS_INTERFACE) &&
10913a3f90c6SAndrew Thompson 		    (desc->bDescriptorSubtype == FORMAT_TYPE) &&
1092e2524b2eSHans Petter Selasky 		    (asf1d.v1 == NULL)) {
1093e2524b2eSHans Petter Selasky 			if (audio_rev >= UAUDIO_VERSION_30) {
1094e2524b2eSHans Petter Selasky 				/* FALLTHROUGH */
1095e2524b2eSHans Petter Selasky 			} else if (audio_rev >= UAUDIO_VERSION_20) {
1096e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*asf1d.v2))
1097e2524b2eSHans Petter Selasky 					asf1d.v2 = (void *)desc;
1098e2524b2eSHans Petter Selasky 			} else {
1099e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*asf1d.v1)) {
1100e2524b2eSHans Petter Selasky 					asf1d.v1 = (void *)desc;
1101e2524b2eSHans Petter Selasky 
1102e2524b2eSHans Petter Selasky 					if (asf1d.v1->bFormatType != FORMAT_TYPE_I) {
11033a3f90c6SAndrew Thompson 						DPRINTFN(11, "ignored bFormatType = %d\n",
1104e2524b2eSHans Petter Selasky 						    asf1d.v1->bFormatType);
1105e2524b2eSHans Petter Selasky 						asf1d.v1 = NULL;
11063a3f90c6SAndrew Thompson 						continue;
11073a3f90c6SAndrew Thompson 					}
1108e2524b2eSHans Petter Selasky 					if (desc->bLength < (sizeof(*asf1d.v1) +
1109e2524b2eSHans Petter Selasky 					    ((asf1d.v1->bSamFreqType == 0) ? 6 :
1110e2524b2eSHans Petter Selasky 					    (asf1d.v1->bSamFreqType * 3)))) {
1111e2524b2eSHans Petter Selasky 						DPRINTFN(11, "invalid descriptor, "
1112e2524b2eSHans Petter Selasky 						    "too short\n");
1113e2524b2eSHans Petter Selasky 						asf1d.v1 = NULL;
11143a3f90c6SAndrew Thompson 						continue;
11153a3f90c6SAndrew Thompson 					}
11163a3f90c6SAndrew Thompson 				}
11173a3f90c6SAndrew Thompson 			}
1118e2524b2eSHans Petter Selasky 		}
11193a3f90c6SAndrew Thompson 		if ((desc->bDescriptorType == UDESC_ENDPOINT) &&
1120e2524b2eSHans Petter Selasky 		    (desc->bLength >= UEP_MINSIZE) &&
1121e2524b2eSHans Petter Selasky 		    (ed1 == NULL)) {
11223a3f90c6SAndrew Thompson 			ed1 = (void *)desc;
11233a3f90c6SAndrew Thompson 			if (UE_GET_XFERTYPE(ed1->bmAttributes) != UE_ISOCHRONOUS) {
11243a3f90c6SAndrew Thompson 				ed1 = NULL;
1125e2524b2eSHans Petter Selasky 				continue;
11263a3f90c6SAndrew Thompson 			}
11273a3f90c6SAndrew Thompson 		}
1128e2524b2eSHans Petter Selasky 		if ((acdp != NULL) &&
1129e2524b2eSHans Petter Selasky 		    (desc->bDescriptorType == UDESC_CS_ENDPOINT) &&
11303a3f90c6SAndrew Thompson 		    (desc->bDescriptorSubtype == AS_GENERAL) &&
1131e2524b2eSHans Petter Selasky 		    (sed.v1 == NULL)) {
1132e2524b2eSHans Petter Selasky 			if (audio_rev >= UAUDIO_VERSION_30) {
1133e2524b2eSHans Petter Selasky 				/* FALLTHROUGH */
1134e2524b2eSHans Petter Selasky 			} else if (audio_rev >= UAUDIO_VERSION_20) {
1135e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*sed.v2))
1136e2524b2eSHans Petter Selasky 					sed.v2 = (void *)desc;
1137e2524b2eSHans Petter Selasky 			} else {
1138e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*sed.v1))
1139e2524b2eSHans Petter Selasky 					sed.v1 = (void *)desc;
11403a3f90c6SAndrew Thompson 			}
11413a3f90c6SAndrew Thompson 		}
11421234097eSHans Petter Selasky 		if (asid.v1 == NULL || asf1d.v1 == NULL ||
11431234097eSHans Petter Selasky 		    ed1 == NULL || sed.v1 == NULL) {
1144e2524b2eSHans Petter Selasky 			/* need more descriptors */
1145e2524b2eSHans Petter Selasky 			continue;
1146e2524b2eSHans Petter Selasky 		}
11473a3f90c6SAndrew Thompson 
11483a3f90c6SAndrew Thompson 		ep_dir = UE_GET_DIR(ed1->bEndpointAddress);
11493a3f90c6SAndrew Thompson 
1150e3e05e50SAndrew Thompson 		/* We ignore sync endpoint information until further. */
11513a3f90c6SAndrew Thompson 
1152e2524b2eSHans Petter Selasky 		if (audio_rev >= UAUDIO_VERSION_30) {
1153e2524b2eSHans Petter Selasky 			goto next_ep;
1154e2524b2eSHans Petter Selasky 		} else if (audio_rev >= UAUDIO_VERSION_20) {
11553a3f90c6SAndrew Thompson 
1156e2524b2eSHans Petter Selasky 			uint32_t dwFormat;
1157e2524b2eSHans Petter Selasky 			uint8_t bSubslotSize;
11583a3f90c6SAndrew Thompson 
1159e2524b2eSHans Petter Selasky 			dwFormat = UGETDW(asid.v2->bmFormats);
1160e2524b2eSHans Petter Selasky 			bChannels = asid.v2->bNrChannels;
1161e2524b2eSHans Petter Selasky 			bBitResolution = asf1d.v2->bBitResolution;
1162e2524b2eSHans Petter Selasky 			bSubslotSize = asf1d.v2->bSubslotSize;
1163e2524b2eSHans Petter Selasky 
1164e2524b2eSHans Petter Selasky 			if (bBitResolution != (bSubslotSize * 8)) {
1165e2524b2eSHans Petter Selasky 				DPRINTF("Invalid bSubslotSize\n");
1166e2524b2eSHans Petter Selasky 				goto next_ep;
1167e2524b2eSHans Petter Selasky 			}
1168e2524b2eSHans Petter Selasky 
1169e2524b2eSHans Petter Selasky 			if ((bChannels != channels) ||
1170e2524b2eSHans Petter Selasky 			    (bBitResolution != bit_resolution)) {
1171e2524b2eSHans Petter Selasky 				DPRINTF("Wrong number of channels\n");
1172e2524b2eSHans Petter Selasky 				goto next_ep;
1173e2524b2eSHans Petter Selasky 			}
1174e2524b2eSHans Petter Selasky 
1175e2524b2eSHans Petter Selasky 			for (p_fmt = uaudio20_formats;
1176e2524b2eSHans Petter Selasky 			    p_fmt->wFormat != 0; p_fmt++) {
1177e2524b2eSHans Petter Selasky 				if ((p_fmt->wFormat & dwFormat) &&
1178e2524b2eSHans Petter Selasky 				    (p_fmt->bPrecision == bBitResolution))
1179e2524b2eSHans Petter Selasky 					break;
1180e2524b2eSHans Petter Selasky 			}
1181e2524b2eSHans Petter Selasky 
1182e2524b2eSHans Petter Selasky 			if (p_fmt->wFormat == 0) {
1183e2524b2eSHans Petter Selasky 				DPRINTF("Unsupported audio format\n");
1184e2524b2eSHans Petter Selasky 				goto next_ep;
1185e2524b2eSHans Petter Selasky 			}
1186e2524b2eSHans Petter Selasky 
1187e2524b2eSHans Petter Selasky 			for (x = 0; x != 256; x++) {
1188e2524b2eSHans Petter Selasky 				if (ep_dir == UE_DIR_OUT) {
1189e2524b2eSHans Petter Selasky 					if (!(sc->sc_mixer_clocks.bit_output[x / 8] &
1190e2524b2eSHans Petter Selasky 					    (1 << (x % 8)))) {
1191e2524b2eSHans Petter Selasky 						continue;
11923a3f90c6SAndrew Thompson 					}
11933a3f90c6SAndrew Thompson 				} else {
1194e2524b2eSHans Petter Selasky 					if (!(sc->sc_mixer_clocks.bit_input[x / 8] &
1195e2524b2eSHans Petter Selasky 					    (1 << (x % 8)))) {
1196e2524b2eSHans Petter Selasky 						continue;
1197e2524b2eSHans Petter Selasky 					}
1198e2524b2eSHans Petter Selasky 				}
11993a3f90c6SAndrew Thompson 
1200e2524b2eSHans Petter Selasky 				DPRINTF("Checking clock ID=%d\n", x);
1201e2524b2eSHans Petter Selasky 
1202e2524b2eSHans Petter Selasky 				if (uaudio20_check_rate(udev,
1203e2524b2eSHans Petter Selasky 				    sc->sc_mixer_iface_no, x, rate)) {
1204e2524b2eSHans Petter Selasky 					DPRINTF("Unsupported sampling "
1205e2524b2eSHans Petter Selasky 					    "rate, id=%d\n", x);
1206e2524b2eSHans Petter Selasky 					goto next_ep;
1207e2524b2eSHans Petter Selasky 				}
1208e2524b2eSHans Petter Selasky 			}
1209e2524b2eSHans Petter Selasky 		} else {
1210e2524b2eSHans Petter Selasky 			uint16_t wFormat;
1211e2524b2eSHans Petter Selasky 
1212e2524b2eSHans Petter Selasky 			wFormat = UGETW(asid.v1->wFormatTag);
1213e2524b2eSHans Petter Selasky 			bChannels = UAUDIO_MAX_CHAN(asf1d.v1->bNrChannels);
1214e2524b2eSHans Petter Selasky 			bBitResolution = asf1d.v1->bBitResolution;
1215e2524b2eSHans Petter Selasky 
1216e2524b2eSHans Petter Selasky 			if (asf1d.v1->bSamFreqType == 0) {
1217e2524b2eSHans Petter Selasky 				DPRINTFN(16, "Sample rate: %d-%dHz\n",
1218e2524b2eSHans Petter Selasky 				    UA_SAMP_LO(asf1d.v1),
1219e2524b2eSHans Petter Selasky 				    UA_SAMP_HI(asf1d.v1));
1220e2524b2eSHans Petter Selasky 
1221e2524b2eSHans Petter Selasky 				if ((rate >= UA_SAMP_LO(asf1d.v1)) &&
1222e2524b2eSHans Petter Selasky 				    (rate <= UA_SAMP_HI(asf1d.v1)))
1223e2524b2eSHans Petter Selasky 					goto found_rate;
1224e2524b2eSHans Petter Selasky 			} else {
1225e2524b2eSHans Petter Selasky 
1226e2524b2eSHans Petter Selasky 				for (x = 0; x < asf1d.v1->bSamFreqType; x++) {
12273a3f90c6SAndrew Thompson 					DPRINTFN(16, "Sample rate = %dHz\n",
1228e2524b2eSHans Petter Selasky 					    UA_GETSAMP(asf1d.v1, x));
12293a3f90c6SAndrew Thompson 
1230e2524b2eSHans Petter Selasky 					if (rate == UA_GETSAMP(asf1d.v1, x))
12313a3f90c6SAndrew Thompson 						goto found_rate;
12323a3f90c6SAndrew Thompson 				}
12333a3f90c6SAndrew Thompson 			}
1234e2524b2eSHans Petter Selasky 			goto next_ep;
12353a3f90c6SAndrew Thompson 
12363a3f90c6SAndrew Thompson 	found_rate:
1237e2524b2eSHans Petter Selasky 			for (p_fmt = uaudio10_formats;
1238e2524b2eSHans Petter Selasky 			    p_fmt->wFormat != 0; p_fmt++) {
12393a3f90c6SAndrew Thompson 				if ((p_fmt->wFormat == wFormat) &&
1240e2524b2eSHans Petter Selasky 				    (p_fmt->bPrecision == bBitResolution))
1241e2524b2eSHans Petter Selasky 					break;
12423a3f90c6SAndrew Thompson 			}
1243e2524b2eSHans Petter Selasky 			if (p_fmt->wFormat == 0) {
1244e2524b2eSHans Petter Selasky 				DPRINTF("Unsupported audio format\n");
1245e2524b2eSHans Petter Selasky 				goto next_ep;
12463a3f90c6SAndrew Thompson 			}
12473a3f90c6SAndrew Thompson 
1248e2524b2eSHans Petter Selasky 			if ((bChannels != channels) ||
1249e2524b2eSHans Petter Selasky 			    (bBitResolution != bit_resolution)) {
1250e2524b2eSHans Petter Selasky 				DPRINTF("Wrong number of channels\n");
1251e2524b2eSHans Petter Selasky 				goto next_ep;
1252e2524b2eSHans Petter Selasky 			}
1253e2524b2eSHans Petter Selasky 		}
12543a3f90c6SAndrew Thompson 
12553a3f90c6SAndrew Thompson 		chan = (ep_dir == UE_DIR_IN) ?
1256e2524b2eSHans Petter Selasky 		    &sc->sc_rec_chan : &sc->sc_play_chan;
12573a3f90c6SAndrew Thompson 
1258e2524b2eSHans Petter Selasky 		if (chan->valid != 0 ||
1259e2524b2eSHans Petter Selasky 		    usbd_get_iface(udev, curidx) == NULL) {
1260e2524b2eSHans Petter Selasky 			DPRINTF("Channel already exists or "
1261e2524b2eSHans Petter Selasky 			    "interface is not valid\n");
1262e2524b2eSHans Petter Selasky 			goto next_ep;
1263e2524b2eSHans Petter Selasky 		}
12643a3f90c6SAndrew Thompson 
12653a3f90c6SAndrew Thompson 		chan->valid = 1;
1266b850ecc1SAndrew Thompson #ifdef USB_DEBUG
12673a3f90c6SAndrew Thompson 		uaudio_chan_dump_ep_desc(ed1);
12683a3f90c6SAndrew Thompson #endif
12693a3f90c6SAndrew Thompson 		DPRINTF("Sample rate = %dHz, channels = %d, "
12703a3f90c6SAndrew Thompson 		    "bits = %d, format = %s\n", rate, channels,
12713a3f90c6SAndrew Thompson 		    bit_resolution, p_fmt->description);
12723a3f90c6SAndrew Thompson 
12733a3f90c6SAndrew Thompson 		chan->sample_rate = rate;
12743a3f90c6SAndrew Thompson 		chan->p_asf1d = asf1d;
12753a3f90c6SAndrew Thompson 		chan->p_ed1 = ed1;
12763a3f90c6SAndrew Thompson 		chan->p_fmt = p_fmt;
12773a3f90c6SAndrew Thompson 		chan->p_sed = sed;
12783a3f90c6SAndrew Thompson 		chan->iface_index = curidx;
12793a3f90c6SAndrew Thompson 		chan->iface_alt_index = alt_index;
12803a3f90c6SAndrew Thompson 
12813a3f90c6SAndrew Thompson 		if (ep_dir == UE_DIR_IN)
1282e2524b2eSHans Petter Selasky 			chan->usb_cfg = uaudio_cfg_record;
12833a3f90c6SAndrew Thompson 		else
1284e2524b2eSHans Petter Selasky 			chan->usb_cfg = uaudio_cfg_play;
12853a3f90c6SAndrew Thompson 
1286e2524b2eSHans Petter Selasky 		chan->sample_size = (UAUDIO_MAX_CHAN(channels) *
1287e2524b2eSHans Petter Selasky 		    p_fmt->bPrecision) / 8;
1288e2524b2eSHans Petter Selasky 		chan->channels = channels;
12893a3f90c6SAndrew Thompson 
1290f895cc0cSHans Petter Selasky 		if (ep_dir == UE_DIR_IN &&
1291f895cc0cSHans Petter Selasky 		    usbd_get_speed(udev) == USB_SPEED_FULL) {
1292f895cc0cSHans Petter Selasky 			uaudio_record_fix_fs(ed1,
1293f895cc0cSHans Petter Selasky 			    chan->sample_size * (rate / 1000),
1294f895cc0cSHans Petter Selasky 			    chan->sample_size * (rate / 4000));
1295f895cc0cSHans Petter Selasky 		}
1296f895cc0cSHans Petter Selasky 
1297e2524b2eSHans Petter Selasky 		if (sc->sc_sndstat_valid != 0) {
12983a3f90c6SAndrew Thompson 			sbuf_printf(&sc->sc_sndstat, "\n\t"
1299e2524b2eSHans Petter Selasky 			    "mode %d.%d:(%s) %dch, %dbit, %s, %dHz",
13003a3f90c6SAndrew Thompson 			    curidx, alt_index,
13013a3f90c6SAndrew Thompson 			    (ep_dir == UE_DIR_IN) ? "input" : "output",
1302e2524b2eSHans Petter Selasky 				    channels, p_fmt->bPrecision,
13033a3f90c6SAndrew Thompson 				    p_fmt->description, rate);
13043a3f90c6SAndrew Thompson 		}
1305e2524b2eSHans Petter Selasky 
1306e2524b2eSHans Petter Selasky 	next_ep:
1307e2524b2eSHans Petter Selasky 		sed.v1 = NULL;
1308e2524b2eSHans Petter Selasky 		ed1 = NULL;
13093a3f90c6SAndrew Thompson 	}
13103a3f90c6SAndrew Thompson }
13113a3f90c6SAndrew Thompson 
1312afbfddd9SAndrew Thompson /* This structure defines all the supported rates. */
1313afbfddd9SAndrew Thompson 
1314afbfddd9SAndrew Thompson static const uint32_t uaudio_rate_list[] = {
1315afbfddd9SAndrew Thompson 	96000,
1316afbfddd9SAndrew Thompson 	88000,
1317afbfddd9SAndrew Thompson 	80000,
1318afbfddd9SAndrew Thompson 	72000,
1319afbfddd9SAndrew Thompson 	64000,
1320afbfddd9SAndrew Thompson 	56000,
1321afbfddd9SAndrew Thompson 	48000,
1322afbfddd9SAndrew Thompson 	44100,
1323afbfddd9SAndrew Thompson 	40000,
1324afbfddd9SAndrew Thompson 	32000,
1325afbfddd9SAndrew Thompson 	24000,
1326afbfddd9SAndrew Thompson 	22050,
1327afbfddd9SAndrew Thompson 	16000,
1328afbfddd9SAndrew Thompson 	11025,
1329afbfddd9SAndrew Thompson 	8000,
1330afbfddd9SAndrew Thompson 	0
1331afbfddd9SAndrew Thompson };
1332afbfddd9SAndrew Thompson 
13333a3f90c6SAndrew Thompson static void
1334760bc48eSAndrew Thompson uaudio_chan_fill_info(struct uaudio_softc *sc, struct usb_device *udev)
13353a3f90c6SAndrew Thompson {
13363a3f90c6SAndrew Thompson 	uint32_t rate = uaudio_default_rate;
1337afbfddd9SAndrew Thompson 	uint8_t z;
13383a3f90c6SAndrew Thompson 	uint8_t bits = uaudio_default_bits;
13393a3f90c6SAndrew Thompson 	uint8_t y;
13403a3f90c6SAndrew Thompson 	uint8_t channels = uaudio_default_channels;
13413a3f90c6SAndrew Thompson 	uint8_t x;
13423a3f90c6SAndrew Thompson 
13433a3f90c6SAndrew Thompson 	bits -= (bits % 8);
13443a3f90c6SAndrew Thompson 	if ((bits == 0) || (bits > 32)) {
13453a3f90c6SAndrew Thompson 		/* set a valid value */
13463a3f90c6SAndrew Thompson 		bits = 32;
13473a3f90c6SAndrew Thompson 	}
1348afbfddd9SAndrew Thompson 	if (channels == 0) {
1349afbfddd9SAndrew Thompson 		switch (usbd_get_speed(udev)) {
1350afbfddd9SAndrew Thompson 		case USB_SPEED_LOW:
1351afbfddd9SAndrew Thompson 		case USB_SPEED_FULL:
1352afbfddd9SAndrew Thompson 			/*
1353afbfddd9SAndrew Thompson 			 * Due to high bandwidth usage and problems
1354afbfddd9SAndrew Thompson 			 * with HIGH-speed split transactions we
1355afbfddd9SAndrew Thompson 			 * disable surround setups on FULL-speed USB
1356afbfddd9SAndrew Thompson 			 * by default
1357afbfddd9SAndrew Thompson 			 */
13581234097eSHans Petter Selasky 			channels = 4;
1359afbfddd9SAndrew Thompson 			break;
1360afbfddd9SAndrew Thompson 		default:
1361afbfddd9SAndrew Thompson 			channels = 16;
1362afbfddd9SAndrew Thompson 			break;
1363afbfddd9SAndrew Thompson 		}
1364afbfddd9SAndrew Thompson 	} else if (channels > 16) {
1365afbfddd9SAndrew Thompson 		channels = 16;
13663a3f90c6SAndrew Thompson 	}
13673a3f90c6SAndrew Thompson 	if (sbuf_new(&sc->sc_sndstat, NULL, 4096, SBUF_AUTOEXTEND)) {
13683a3f90c6SAndrew Thompson 		sc->sc_sndstat_valid = 1;
13693a3f90c6SAndrew Thompson 	}
13703a3f90c6SAndrew Thompson 	/* try to search for a valid config */
13713a3f90c6SAndrew Thompson 
13723a3f90c6SAndrew Thompson 	for (x = channels; x; x--) {
13733a3f90c6SAndrew Thompson 		for (y = bits; y; y -= 8) {
1374afbfddd9SAndrew Thompson 
1375afbfddd9SAndrew Thompson 			/* try user defined rate, if any */
1376afbfddd9SAndrew Thompson 			if (rate != 0)
1377afbfddd9SAndrew Thompson 				uaudio_chan_fill_info_sub(sc, udev, rate, x, y);
1378afbfddd9SAndrew Thompson 
1379afbfddd9SAndrew Thompson 			/* try find a matching rate, if any */
1380afbfddd9SAndrew Thompson 			for (z = 0; uaudio_rate_list[z]; z++) {
1381afbfddd9SAndrew Thompson 				uaudio_chan_fill_info_sub(sc, udev, uaudio_rate_list[z], x, y);
13823a3f90c6SAndrew Thompson 
13833a3f90c6SAndrew Thompson 				if (sc->sc_rec_chan.valid &&
13843a3f90c6SAndrew Thompson 				    sc->sc_play_chan.valid) {
13853a3f90c6SAndrew Thompson 					goto done;
13863a3f90c6SAndrew Thompson 				}
13873a3f90c6SAndrew Thompson 			}
13883a3f90c6SAndrew Thompson 		}
13893a3f90c6SAndrew Thompson 	}
13903a3f90c6SAndrew Thompson 
13913a3f90c6SAndrew Thompson done:
13923a3f90c6SAndrew Thompson 	if (sc->sc_sndstat_valid) {
13933a3f90c6SAndrew Thompson 		sbuf_finish(&sc->sc_sndstat);
13943a3f90c6SAndrew Thompson 	}
13953a3f90c6SAndrew Thompson }
13963a3f90c6SAndrew Thompson 
13973a3f90c6SAndrew Thompson static void
1398ed6d949aSAndrew Thompson uaudio_chan_play_callback(struct usb_xfer *xfer, usb_error_t error)
13993a3f90c6SAndrew Thompson {
1400ed6d949aSAndrew Thompson 	struct uaudio_chan *ch = usbd_xfer_softc(xfer);
1401ed6d949aSAndrew Thompson 	struct usb_page_cache *pc;
14023a3f90c6SAndrew Thompson 	uint32_t total;
14033a3f90c6SAndrew Thompson 	uint32_t blockcount;
14043a3f90c6SAndrew Thompson 	uint32_t n;
14053a3f90c6SAndrew Thompson 	uint32_t offset;
1406afbfddd9SAndrew Thompson 	int actlen;
1407afbfddd9SAndrew Thompson 	int sumlen;
1408ed6d949aSAndrew Thompson 
1409ed6d949aSAndrew Thompson 	usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
14103a3f90c6SAndrew Thompson 
1411b029f6bbSAndrew Thompson 	if (ch->end == ch->start) {
1412b029f6bbSAndrew Thompson 		DPRINTF("no buffer!\n");
1413b029f6bbSAndrew Thompson 		return;
1414b029f6bbSAndrew Thompson 	}
14153a3f90c6SAndrew Thompson 
14163a3f90c6SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
14173a3f90c6SAndrew Thompson 	case USB_ST_TRANSFERRED:
14183a3f90c6SAndrew Thompson tr_transferred:
1419ed6d949aSAndrew Thompson 		if (actlen < sumlen) {
14203a3f90c6SAndrew Thompson 			DPRINTF("short transfer, "
1421afbfddd9SAndrew Thompson 			    "%d of %d bytes\n", actlen, sumlen);
14223a3f90c6SAndrew Thompson 		}
14233a3f90c6SAndrew Thompson 		chn_intr(ch->pcm_ch);
14243a3f90c6SAndrew Thompson 
14253a3f90c6SAndrew Thompson 	case USB_ST_SETUP:
1426afbfddd9SAndrew Thompson 		if (ch->bytes_per_frame[1] > usbd_xfer_max_framelen(xfer)) {
14273a3f90c6SAndrew Thompson 			DPRINTF("bytes per transfer, %d, "
14283a3f90c6SAndrew Thompson 			    "exceeds maximum, %d!\n",
1429afbfddd9SAndrew Thompson 			    ch->bytes_per_frame[1],
1430ed6d949aSAndrew Thompson 			    usbd_xfer_max_framelen(xfer));
14313a3f90c6SAndrew Thompson 			break;
14323a3f90c6SAndrew Thompson 		}
1433afbfddd9SAndrew Thompson 
1434afbfddd9SAndrew Thompson 		blockcount = ch->intr_frames;
1435afbfddd9SAndrew Thompson 
1436afbfddd9SAndrew Thompson 		/* setup number of frames */
1437ed6d949aSAndrew Thompson 		usbd_xfer_set_frames(xfer, blockcount);
1438afbfddd9SAndrew Thompson 
1439afbfddd9SAndrew Thompson 		/* reset total length */
1440afbfddd9SAndrew Thompson 		total = 0;
1441afbfddd9SAndrew Thompson 
1442afbfddd9SAndrew Thompson 		/* setup frame lengths */
1443afbfddd9SAndrew Thompson 		for (n = 0; n != blockcount; n++) {
1444afbfddd9SAndrew Thompson 			ch->sample_curr += ch->sample_rem;
1445afbfddd9SAndrew Thompson 			if (ch->sample_curr >= ch->frames_per_second) {
1446afbfddd9SAndrew Thompson 				ch->sample_curr -= ch->frames_per_second;
1447afbfddd9SAndrew Thompson 				usbd_xfer_set_frame_len(xfer, n, ch->bytes_per_frame[1]);
1448afbfddd9SAndrew Thompson 				total += ch->bytes_per_frame[1];
1449afbfddd9SAndrew Thompson 			} else {
1450afbfddd9SAndrew Thompson 				usbd_xfer_set_frame_len(xfer, n, ch->bytes_per_frame[0]);
1451afbfddd9SAndrew Thompson 				total += ch->bytes_per_frame[0];
1452afbfddd9SAndrew Thompson 			}
1453afbfddd9SAndrew Thompson 		}
14543a3f90c6SAndrew Thompson 
14553a3f90c6SAndrew Thompson 		DPRINTFN(6, "transfer %d bytes\n", total);
14563a3f90c6SAndrew Thompson 
14573a3f90c6SAndrew Thompson 		offset = 0;
14583a3f90c6SAndrew Thompson 
1459ed6d949aSAndrew Thompson 		pc = usbd_xfer_get_frame(xfer, 0);
14603a3f90c6SAndrew Thompson 		while (total > 0) {
14613a3f90c6SAndrew Thompson 
14623a3f90c6SAndrew Thompson 			n = (ch->end - ch->cur);
14633a3f90c6SAndrew Thompson 			if (n > total) {
14643a3f90c6SAndrew Thompson 				n = total;
14653a3f90c6SAndrew Thompson 			}
1466ed6d949aSAndrew Thompson 			usbd_copy_in(pc, offset, ch->cur, n);
14673a3f90c6SAndrew Thompson 
14683a3f90c6SAndrew Thompson 			total -= n;
14693a3f90c6SAndrew Thompson 			ch->cur += n;
14703a3f90c6SAndrew Thompson 			offset += n;
14713a3f90c6SAndrew Thompson 
14723a3f90c6SAndrew Thompson 			if (ch->cur >= ch->end) {
14733a3f90c6SAndrew Thompson 				ch->cur = ch->start;
14743a3f90c6SAndrew Thompson 			}
14753a3f90c6SAndrew Thompson 		}
14763a3f90c6SAndrew Thompson 
1477a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
14783a3f90c6SAndrew Thompson 		break;
14793a3f90c6SAndrew Thompson 
14803a3f90c6SAndrew Thompson 	default:			/* Error */
1481ed6d949aSAndrew Thompson 		if (error == USB_ERR_CANCELLED) {
14823a3f90c6SAndrew Thompson 			break;
14833a3f90c6SAndrew Thompson 		}
14843a3f90c6SAndrew Thompson 		goto tr_transferred;
14853a3f90c6SAndrew Thompson 	}
14863a3f90c6SAndrew Thompson }
14873a3f90c6SAndrew Thompson 
14883a3f90c6SAndrew Thompson static void
1489ed6d949aSAndrew Thompson uaudio_chan_record_callback(struct usb_xfer *xfer, usb_error_t error)
14903a3f90c6SAndrew Thompson {
1491ed6d949aSAndrew Thompson 	struct uaudio_chan *ch = usbd_xfer_softc(xfer);
1492ed6d949aSAndrew Thompson 	struct usb_page_cache *pc;
14933a3f90c6SAndrew Thompson 	uint32_t offset0;
14943a3f90c6SAndrew Thompson 	uint32_t offset1;
1495b029f6bbSAndrew Thompson 	uint32_t mfl;
14966d917491SHans Petter Selasky 	int m;
14976d917491SHans Petter Selasky 	int n;
1498ed6d949aSAndrew Thompson 	int len;
1499b029f6bbSAndrew Thompson 	int actlen;
1500b029f6bbSAndrew Thompson 	int nframes;
15016d917491SHans Petter Selasky 	int blockcount;
1502ed6d949aSAndrew Thompson 
1503ed6d949aSAndrew Thompson 	usbd_xfer_status(xfer, &actlen, NULL, NULL, &nframes);
1504b029f6bbSAndrew Thompson 	mfl = usbd_xfer_max_framelen(xfer);
15053a3f90c6SAndrew Thompson 
1506b029f6bbSAndrew Thompson 	if (ch->end == ch->start) {
1507b029f6bbSAndrew Thompson 		DPRINTF("no buffer!\n");
1508b029f6bbSAndrew Thompson 		return;
1509b029f6bbSAndrew Thompson 	}
15103a3f90c6SAndrew Thompson 
15113a3f90c6SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
15123a3f90c6SAndrew Thompson 	case USB_ST_TRANSFERRED:
1513afbfddd9SAndrew Thompson 
1514ed6d949aSAndrew Thompson 		DPRINTFN(6, "transferred %d bytes\n", actlen);
15153a3f90c6SAndrew Thompson 
15163a3f90c6SAndrew Thompson 		offset0 = 0;
1517b029f6bbSAndrew Thompson 		pc = usbd_xfer_get_frame(xfer, 0);
15183a3f90c6SAndrew Thompson 
1519ed6d949aSAndrew Thompson 		for (n = 0; n != nframes; n++) {
15203a3f90c6SAndrew Thompson 
15213a3f90c6SAndrew Thompson 			offset1 = offset0;
15228f9e0ef9SAndrew Thompson 			len = usbd_xfer_frame_len(xfer, n);
15233a3f90c6SAndrew Thompson 
1524ed6d949aSAndrew Thompson 			while (len > 0) {
15253a3f90c6SAndrew Thompson 
15263a3f90c6SAndrew Thompson 				m = (ch->end - ch->cur);
15273a3f90c6SAndrew Thompson 
15286d917491SHans Petter Selasky 				if (m > len)
1529ed6d949aSAndrew Thompson 					m = len;
15306d917491SHans Petter Selasky 
1531ed6d949aSAndrew Thompson 				usbd_copy_out(pc, offset1, ch->cur, m);
15323a3f90c6SAndrew Thompson 
1533ed6d949aSAndrew Thompson 				len -= m;
15343a3f90c6SAndrew Thompson 				offset1 += m;
15353a3f90c6SAndrew Thompson 				ch->cur += m;
15363a3f90c6SAndrew Thompson 
15373a3f90c6SAndrew Thompson 				if (ch->cur >= ch->end) {
15383a3f90c6SAndrew Thompson 					ch->cur = ch->start;
15393a3f90c6SAndrew Thompson 				}
15403a3f90c6SAndrew Thompson 			}
15413a3f90c6SAndrew Thompson 
1542b029f6bbSAndrew Thompson 			offset0 += mfl;
15433a3f90c6SAndrew Thompson 		}
15443a3f90c6SAndrew Thompson 
15453a3f90c6SAndrew Thompson 		chn_intr(ch->pcm_ch);
15463a3f90c6SAndrew Thompson 
15473a3f90c6SAndrew Thompson 	case USB_ST_SETUP:
1548b029f6bbSAndrew Thompson tr_setup:
1549afbfddd9SAndrew Thompson 		blockcount = ch->intr_frames;
1550afbfddd9SAndrew Thompson 
1551ed6d949aSAndrew Thompson 		usbd_xfer_set_frames(xfer, blockcount);
1552ed6d949aSAndrew Thompson 		for (n = 0; n < blockcount; n++) {
1553b029f6bbSAndrew Thompson 			usbd_xfer_set_frame_len(xfer, n, mfl);
15543a3f90c6SAndrew Thompson 		}
15553a3f90c6SAndrew Thompson 
1556a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
1557b029f6bbSAndrew Thompson 		break;
15583a3f90c6SAndrew Thompson 
15593a3f90c6SAndrew Thompson 	default:			/* Error */
1560ed6d949aSAndrew Thompson 		if (error == USB_ERR_CANCELLED) {
1561b029f6bbSAndrew Thompson 			break;
15623a3f90c6SAndrew Thompson 		}
1563b029f6bbSAndrew Thompson 		goto tr_setup;
15643a3f90c6SAndrew Thompson 	}
15653a3f90c6SAndrew Thompson }
15663a3f90c6SAndrew Thompson 
15673a3f90c6SAndrew Thompson void   *
15683a3f90c6SAndrew Thompson uaudio_chan_init(struct uaudio_softc *sc, struct snd_dbuf *b,
15693a3f90c6SAndrew Thompson     struct pcm_channel *c, int dir)
15703a3f90c6SAndrew Thompson {
15713a3f90c6SAndrew Thompson 	struct uaudio_chan *ch = ((dir == PCMDIR_PLAY) ?
15723a3f90c6SAndrew Thompson 	    &sc->sc_play_chan : &sc->sc_rec_chan);
15733a3f90c6SAndrew Thompson 	uint32_t buf_size;
1574b029f6bbSAndrew Thompson 	uint32_t frames;
1575afbfddd9SAndrew Thompson 	uint32_t format;
1576afbfddd9SAndrew Thompson 	uint16_t fps;
15773a3f90c6SAndrew Thompson 	uint8_t endpoint;
1578b029f6bbSAndrew Thompson 	uint8_t blocks;
15793a3f90c6SAndrew Thompson 	uint8_t iface_index;
15803a3f90c6SAndrew Thompson 	uint8_t alt_index;
1581b029f6bbSAndrew Thompson 	uint8_t fps_shift;
1582e0a69b51SAndrew Thompson 	usb_error_t err;
15833a3f90c6SAndrew Thompson 
1584afbfddd9SAndrew Thompson 	fps = usbd_get_isoc_fps(sc->sc_udev);
1585afbfddd9SAndrew Thompson 
1586afbfddd9SAndrew Thompson 	if (fps < 8000) {
1587b029f6bbSAndrew Thompson 		/* FULL speed USB */
1588b029f6bbSAndrew Thompson 		frames = 8;
1589b029f6bbSAndrew Thompson 	} else {
1590b029f6bbSAndrew Thompson 		/* HIGH speed USB */
1591b029f6bbSAndrew Thompson 		frames = UAUDIO_NFRAMES;
1592b029f6bbSAndrew Thompson 	}
1593b029f6bbSAndrew Thompson 
15943a3f90c6SAndrew Thompson 	/* setup play/record format */
15953a3f90c6SAndrew Thompson 
15963a3f90c6SAndrew Thompson 	ch->pcm_cap.fmtlist = ch->pcm_format;
15973a3f90c6SAndrew Thompson 
15983a3f90c6SAndrew Thompson 	ch->pcm_format[0] = 0;
15993a3f90c6SAndrew Thompson 	ch->pcm_format[1] = 0;
16003a3f90c6SAndrew Thompson 
16013a3f90c6SAndrew Thompson 	ch->pcm_cap.minspeed = ch->sample_rate;
16023a3f90c6SAndrew Thompson 	ch->pcm_cap.maxspeed = ch->sample_rate;
16033a3f90c6SAndrew Thompson 
16047fb43570SAndrew Thompson 	/* setup mutex and PCM channel */
16057fb43570SAndrew Thompson 
16067fb43570SAndrew Thompson 	ch->pcm_ch = c;
16077fb43570SAndrew Thompson 	ch->pcm_mtx = c->lock;
16087fb43570SAndrew Thompson 
1609afbfddd9SAndrew Thompson 	format = ch->p_fmt->freebsd_fmt;
16103a3f90c6SAndrew Thompson 
1611e2524b2eSHans Petter Selasky 	switch (ch->channels) {
1612afbfddd9SAndrew Thompson 	case 2:
1613afbfddd9SAndrew Thompson 		/* stereo */
1614afbfddd9SAndrew Thompson 		format = SND_FORMAT(format, 2, 0);
1615afbfddd9SAndrew Thompson 		break;
1616afbfddd9SAndrew Thompson 	case 1:
1617afbfddd9SAndrew Thompson 		/* mono */
1618afbfddd9SAndrew Thompson 		format = SND_FORMAT(format, 1, 0);
1619afbfddd9SAndrew Thompson 		break;
1620afbfddd9SAndrew Thompson 	default:
1621afbfddd9SAndrew Thompson 		/* surround and more */
1622afbfddd9SAndrew Thompson 		format = feeder_matrix_default_format(
1623e2524b2eSHans Petter Selasky 		    SND_FORMAT(format, ch->channels, 0));
1624afbfddd9SAndrew Thompson 		break;
1625afbfddd9SAndrew Thompson 	}
1626afbfddd9SAndrew Thompson 
1627afbfddd9SAndrew Thompson 	ch->pcm_cap.fmtlist[0] = format;
16283a3f90c6SAndrew Thompson 	ch->pcm_cap.fmtlist[1] = 0;
16293a3f90c6SAndrew Thompson 
1630afbfddd9SAndrew Thompson 	/* check if format is not supported */
1631afbfddd9SAndrew Thompson 
1632afbfddd9SAndrew Thompson 	if (format == 0) {
1633afbfddd9SAndrew Thompson 		DPRINTF("The selected audio format is not supported\n");
1634afbfddd9SAndrew Thompson 		goto error;
1635afbfddd9SAndrew Thompson 	}
1636afbfddd9SAndrew Thompson 
16373a3f90c6SAndrew Thompson 	/* set alternate interface corresponding to the mode */
16383a3f90c6SAndrew Thompson 
16393a3f90c6SAndrew Thompson 	endpoint = ch->p_ed1->bEndpointAddress;
16403a3f90c6SAndrew Thompson 	iface_index = ch->iface_index;
16413a3f90c6SAndrew Thompson 	alt_index = ch->iface_alt_index;
16423a3f90c6SAndrew Thompson 
16433a3f90c6SAndrew Thompson 	DPRINTF("endpoint=0x%02x, speed=%d, iface=%d alt=%d\n",
16443a3f90c6SAndrew Thompson 	    endpoint, ch->sample_rate, iface_index, alt_index);
16453a3f90c6SAndrew Thompson 
1646a593f6b8SAndrew Thompson 	err = usbd_set_alt_interface_index(sc->sc_udev, iface_index, alt_index);
16473a3f90c6SAndrew Thompson 	if (err) {
16483a3f90c6SAndrew Thompson 		DPRINTF("setting of alternate index failed: %s!\n",
1649a593f6b8SAndrew Thompson 		    usbd_errstr(err));
16503a3f90c6SAndrew Thompson 		goto error;
16513a3f90c6SAndrew Thompson 	}
165225b74dabSHans Petter Selasky 	usbd_set_parent_iface(sc->sc_udev, iface_index,
165325b74dabSHans Petter Selasky 	    sc->sc_mixer_iface_index);
16543a3f90c6SAndrew Thompson 
16553a3f90c6SAndrew Thompson 	/*
1656aca2249dSHans Petter Selasky 	 * Only set the sample rate if the channel reports that it
1657aca2249dSHans Petter Selasky 	 * supports the frequency control.
16583a3f90c6SAndrew Thompson 	 */
1659e2524b2eSHans Petter Selasky 
1660e2524b2eSHans Petter Selasky 	if (sc->sc_audio_rev >= UAUDIO_VERSION_30) {
1661e2524b2eSHans Petter Selasky 		/* FALLTHROUGH */
1662e2524b2eSHans Petter Selasky 	} else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) {
1663e2524b2eSHans Petter Selasky 		unsigned int x;
1664e2524b2eSHans Petter Selasky 
1665e2524b2eSHans Petter Selasky 		for (x = 0; x != 256; x++) {
1666e2524b2eSHans Petter Selasky 			if (dir == PCMDIR_PLAY) {
1667e2524b2eSHans Petter Selasky 				if (!(sc->sc_mixer_clocks.bit_output[x / 8] &
1668e2524b2eSHans Petter Selasky 				    (1 << (x % 8)))) {
1669e2524b2eSHans Petter Selasky 					continue;
1670e2524b2eSHans Petter Selasky 				}
1671e2524b2eSHans Petter Selasky 			} else {
1672e2524b2eSHans Petter Selasky 				if (!(sc->sc_mixer_clocks.bit_input[x / 8] &
1673e2524b2eSHans Petter Selasky 				    (1 << (x % 8)))) {
1674e2524b2eSHans Petter Selasky 					continue;
1675e2524b2eSHans Petter Selasky 				}
1676e2524b2eSHans Petter Selasky 			}
1677e2524b2eSHans Petter Selasky 
1678e2524b2eSHans Petter Selasky 			if (uaudio20_set_speed(sc->sc_udev,
1679e2524b2eSHans Petter Selasky 			    sc->sc_mixer_iface_no, x, ch->sample_rate)) {
1680e2524b2eSHans Petter Selasky 				/*
1681e2524b2eSHans Petter Selasky 				 * If the endpoint is adaptive setting
1682e2524b2eSHans Petter Selasky 				 * the speed may fail.
1683e2524b2eSHans Petter Selasky 				 */
1684e2524b2eSHans Petter Selasky 				DPRINTF("setting of sample rate failed! "
1685e2524b2eSHans Petter Selasky 				    "(continuing anyway)\n");
1686e2524b2eSHans Petter Selasky 			}
1687e2524b2eSHans Petter Selasky 		}
1688e2524b2eSHans Petter Selasky 	} else if (ch->p_sed.v1->bmAttributes & UA_SED_FREQ_CONTROL) {
16893a3f90c6SAndrew Thompson 		if (uaudio_set_speed(sc->sc_udev, endpoint, ch->sample_rate)) {
16903a3f90c6SAndrew Thompson 			/*
1691e2524b2eSHans Petter Selasky 			 * If the endpoint is adaptive setting the
1692e2524b2eSHans Petter Selasky 			 * speed may fail.
16933a3f90c6SAndrew Thompson 			 */
1694e2524b2eSHans Petter Selasky 			DPRINTF("setting of sample rate failed! "
1695e2524b2eSHans Petter Selasky 			    "(continuing anyway)\n");
16963a3f90c6SAndrew Thompson 		}
16973a3f90c6SAndrew Thompson 	}
1698a593f6b8SAndrew Thompson 	if (usbd_transfer_setup(sc->sc_udev, &iface_index, ch->xfer,
16994c21be9bSRebecca Cran 	    ch->usb_cfg, UAUDIO_NCHANBUFS, ch, ch->pcm_mtx)) {
17003a3f90c6SAndrew Thompson 		DPRINTF("could not allocate USB transfers!\n");
17013a3f90c6SAndrew Thompson 		goto error;
17023a3f90c6SAndrew Thompson 	}
1703b029f6bbSAndrew Thompson 
1704b029f6bbSAndrew Thompson 	fps_shift = usbd_xfer_get_fps_shift(ch->xfer[0]);
1705b029f6bbSAndrew Thompson 
1706afbfddd9SAndrew Thompson 	/* down shift number of frames per second, if any */
1707afbfddd9SAndrew Thompson 	fps >>= fps_shift;
1708afbfddd9SAndrew Thompson 	frames >>= fps_shift;
1709afbfddd9SAndrew Thompson 
1710afbfddd9SAndrew Thompson 	/* bytes per frame should not be zero */
1711afbfddd9SAndrew Thompson 	ch->bytes_per_frame[0] = ((ch->sample_rate / fps) * ch->sample_size);
1712afbfddd9SAndrew Thompson 	ch->bytes_per_frame[1] = (((ch->sample_rate + fps - 1) / fps) * ch->sample_size);
1713afbfddd9SAndrew Thompson 
1714afbfddd9SAndrew Thompson 	/* setup data rate dithering, if any */
1715afbfddd9SAndrew Thompson 	ch->frames_per_second = fps;
1716afbfddd9SAndrew Thompson 	ch->sample_rem = ch->sample_rate % fps;
1717afbfddd9SAndrew Thompson 	ch->sample_curr = 0;
1718afbfddd9SAndrew Thompson 	ch->frames_per_second = fps;
1719afbfddd9SAndrew Thompson 
1720afbfddd9SAndrew Thompson 	/* compute required buffer size */
1721afbfddd9SAndrew Thompson 	buf_size = (ch->bytes_per_frame[1] * frames);
1722afbfddd9SAndrew Thompson 
1723b029f6bbSAndrew Thompson 	ch->intr_size = buf_size;
1724afbfddd9SAndrew Thompson 	ch->intr_frames = frames;
1725afbfddd9SAndrew Thompson 
1726afbfddd9SAndrew Thompson 	DPRINTF("fps=%d sample_rem=%d\n", fps, ch->sample_rem);
1727b029f6bbSAndrew Thompson 
1728b029f6bbSAndrew Thompson 	if (ch->intr_frames == 0) {
1729b029f6bbSAndrew Thompson 		DPRINTF("frame shift is too high!\n");
1730b029f6bbSAndrew Thompson 		goto error;
1731b029f6bbSAndrew Thompson 	}
1732b029f6bbSAndrew Thompson 
1733b029f6bbSAndrew Thompson 	/* setup double buffering */
1734b029f6bbSAndrew Thompson 	buf_size *= 2;
1735b029f6bbSAndrew Thompson 	blocks = 2;
1736b029f6bbSAndrew Thompson 
1737b029f6bbSAndrew Thompson 	ch->buf = malloc(buf_size, M_DEVBUF, M_WAITOK | M_ZERO);
1738b029f6bbSAndrew Thompson 	if (ch->buf == NULL)
1739b029f6bbSAndrew Thompson 		goto error;
1740b029f6bbSAndrew Thompson 	if (sndbuf_setup(b, ch->buf, buf_size) != 0)
1741b029f6bbSAndrew Thompson 		goto error;
1742b029f6bbSAndrew Thompson 	if (sndbuf_resize(b, blocks, ch->intr_size))
1743b029f6bbSAndrew Thompson 		goto error;
1744b029f6bbSAndrew Thompson 
1745b029f6bbSAndrew Thompson 	ch->start = ch->buf;
1746b029f6bbSAndrew Thompson 	ch->end = ch->buf + buf_size;
1747b029f6bbSAndrew Thompson 	ch->cur = ch->buf;
1748b029f6bbSAndrew Thompson 	ch->pcm_buf = b;
1749b029f6bbSAndrew Thompson 
1750b029f6bbSAndrew Thompson 	if (ch->pcm_mtx == NULL) {
1751b029f6bbSAndrew Thompson 		DPRINTF("ERROR: PCM channels does not have a mutex!\n");
1752b029f6bbSAndrew Thompson 		goto error;
1753b029f6bbSAndrew Thompson 	}
1754b029f6bbSAndrew Thompson 
17553a3f90c6SAndrew Thompson 	return (ch);
17563a3f90c6SAndrew Thompson 
17573a3f90c6SAndrew Thompson error:
17583a3f90c6SAndrew Thompson 	uaudio_chan_free(ch);
17593a3f90c6SAndrew Thompson 	return (NULL);
17603a3f90c6SAndrew Thompson }
17613a3f90c6SAndrew Thompson 
17623a3f90c6SAndrew Thompson int
17633a3f90c6SAndrew Thompson uaudio_chan_free(struct uaudio_chan *ch)
17643a3f90c6SAndrew Thompson {
17653a3f90c6SAndrew Thompson 	if (ch->buf != NULL) {
17663a3f90c6SAndrew Thompson 		free(ch->buf, M_DEVBUF);
17673a3f90c6SAndrew Thompson 		ch->buf = NULL;
17683a3f90c6SAndrew Thompson 	}
1769a593f6b8SAndrew Thompson 	usbd_transfer_unsetup(ch->xfer, UAUDIO_NCHANBUFS);
17703a3f90c6SAndrew Thompson 
17713a3f90c6SAndrew Thompson 	ch->valid = 0;
17723a3f90c6SAndrew Thompson 
17733a3f90c6SAndrew Thompson 	return (0);
17743a3f90c6SAndrew Thompson }
17753a3f90c6SAndrew Thompson 
17763a3f90c6SAndrew Thompson int
17773a3f90c6SAndrew Thompson uaudio_chan_set_param_blocksize(struct uaudio_chan *ch, uint32_t blocksize)
17783a3f90c6SAndrew Thompson {
1779b029f6bbSAndrew Thompson 	return (ch->intr_size);
17803a3f90c6SAndrew Thompson }
17813a3f90c6SAndrew Thompson 
17823a3f90c6SAndrew Thompson int
17833a3f90c6SAndrew Thompson uaudio_chan_set_param_fragments(struct uaudio_chan *ch, uint32_t blocksize,
17843a3f90c6SAndrew Thompson     uint32_t blockcount)
17853a3f90c6SAndrew Thompson {
17863a3f90c6SAndrew Thompson 	return (1);
17873a3f90c6SAndrew Thompson }
17883a3f90c6SAndrew Thompson 
17893a3f90c6SAndrew Thompson int
17903a3f90c6SAndrew Thompson uaudio_chan_set_param_speed(struct uaudio_chan *ch, uint32_t speed)
17913a3f90c6SAndrew Thompson {
17923a3f90c6SAndrew Thompson 	if (speed != ch->sample_rate) {
17933a3f90c6SAndrew Thompson 		DPRINTF("rate conversion required\n");
17943a3f90c6SAndrew Thompson 	}
17953a3f90c6SAndrew Thompson 	return (ch->sample_rate);
17963a3f90c6SAndrew Thompson }
17973a3f90c6SAndrew Thompson 
17983a3f90c6SAndrew Thompson int
17993a3f90c6SAndrew Thompson uaudio_chan_getptr(struct uaudio_chan *ch)
18003a3f90c6SAndrew Thompson {
18013a3f90c6SAndrew Thompson 	return (ch->cur - ch->start);
18023a3f90c6SAndrew Thompson }
18033a3f90c6SAndrew Thompson 
18043a3f90c6SAndrew Thompson struct pcmchan_caps *
18053a3f90c6SAndrew Thompson uaudio_chan_getcaps(struct uaudio_chan *ch)
18063a3f90c6SAndrew Thompson {
18073a3f90c6SAndrew Thompson 	return (&ch->pcm_cap);
18083a3f90c6SAndrew Thompson }
18093a3f90c6SAndrew Thompson 
181090da2b28SAriff Abdullah static struct pcmchan_matrix uaudio_chan_matrix_swap_2_0 = {
181190da2b28SAriff Abdullah 	.id = SND_CHN_MATRIX_DRV,
181290da2b28SAriff Abdullah 	.channels = 2,
181390da2b28SAriff Abdullah 	.ext = 0,
181490da2b28SAriff Abdullah 	.map = {
181590da2b28SAriff Abdullah 		/* Right */
181690da2b28SAriff Abdullah 		[0] = {
181790da2b28SAriff Abdullah 			.type = SND_CHN_T_FR,
181890da2b28SAriff Abdullah 			.members =
181990da2b28SAriff Abdullah 			    SND_CHN_T_MASK_FR | SND_CHN_T_MASK_FC |
182090da2b28SAriff Abdullah 			    SND_CHN_T_MASK_LF | SND_CHN_T_MASK_BR |
182190da2b28SAriff Abdullah 			    SND_CHN_T_MASK_BC | SND_CHN_T_MASK_SR
182290da2b28SAriff Abdullah 		},
182390da2b28SAriff Abdullah 		/* Left */
182490da2b28SAriff Abdullah 		[1] = {
182590da2b28SAriff Abdullah 			.type = SND_CHN_T_FL,
182690da2b28SAriff Abdullah 			.members =
182790da2b28SAriff Abdullah 			    SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FC |
182890da2b28SAriff Abdullah 			    SND_CHN_T_MASK_LF | SND_CHN_T_MASK_BL |
182990da2b28SAriff Abdullah 			    SND_CHN_T_MASK_BC | SND_CHN_T_MASK_SL
183090da2b28SAriff Abdullah 		},
183190da2b28SAriff Abdullah 		[2] = {
183290da2b28SAriff Abdullah 			.type = SND_CHN_T_MAX,
183390da2b28SAriff Abdullah 			.members = 0
183490da2b28SAriff Abdullah 		}
183590da2b28SAriff Abdullah 	},
183690da2b28SAriff Abdullah 	.mask = SND_CHN_T_MASK_FR | SND_CHN_T_MASK_FL,
183790da2b28SAriff Abdullah 	.offset = {  1,  0, -1, -1, -1, -1, -1, -1, -1,
183890da2b28SAriff Abdullah 		    -1, -1, -1, -1, -1, -1, -1, -1, -1  }
183990da2b28SAriff Abdullah };
184090da2b28SAriff Abdullah 
184190da2b28SAriff Abdullah struct pcmchan_matrix *
184290da2b28SAriff Abdullah uaudio_chan_getmatrix(struct uaudio_chan *ch, uint32_t format)
184390da2b28SAriff Abdullah {
184490da2b28SAriff Abdullah 	struct uaudio_softc *sc;
184590da2b28SAriff Abdullah 
184690da2b28SAriff Abdullah 	sc = ch->priv_sc;
184790da2b28SAriff Abdullah 
184890da2b28SAriff Abdullah 	if (sc != NULL && sc->sc_uq_audio_swap_lr != 0 &&
184990da2b28SAriff Abdullah 	    AFMT_CHANNEL(format) == 2)
185090da2b28SAriff Abdullah 		return (&uaudio_chan_matrix_swap_2_0);
185190da2b28SAriff Abdullah 
185290da2b28SAriff Abdullah 	return (feeder_matrix_format_map(format));
185390da2b28SAriff Abdullah }
185490da2b28SAriff Abdullah 
18553a3f90c6SAndrew Thompson int
18563a3f90c6SAndrew Thompson uaudio_chan_set_param_format(struct uaudio_chan *ch, uint32_t format)
18573a3f90c6SAndrew Thompson {
18583a3f90c6SAndrew Thompson 	ch->format = format;
18593a3f90c6SAndrew Thompson 	return (0);
18603a3f90c6SAndrew Thompson }
18613a3f90c6SAndrew Thompson 
18623a3f90c6SAndrew Thompson int
18633a3f90c6SAndrew Thompson uaudio_chan_start(struct uaudio_chan *ch)
18643a3f90c6SAndrew Thompson {
18653a3f90c6SAndrew Thompson 	ch->cur = ch->start;
18663a3f90c6SAndrew Thompson 
18673a3f90c6SAndrew Thompson #if (UAUDIO_NCHANBUFS != 2)
18683a3f90c6SAndrew Thompson #error "please update code"
18693a3f90c6SAndrew Thompson #endif
18703a3f90c6SAndrew Thompson 	if (ch->xfer[0]) {
1871a593f6b8SAndrew Thompson 		usbd_transfer_start(ch->xfer[0]);
18723a3f90c6SAndrew Thompson 	}
18733a3f90c6SAndrew Thompson 	if (ch->xfer[1]) {
1874a593f6b8SAndrew Thompson 		usbd_transfer_start(ch->xfer[1]);
18753a3f90c6SAndrew Thompson 	}
18763a3f90c6SAndrew Thompson 	return (0);
18773a3f90c6SAndrew Thompson }
18783a3f90c6SAndrew Thompson 
18793a3f90c6SAndrew Thompson int
18803a3f90c6SAndrew Thompson uaudio_chan_stop(struct uaudio_chan *ch)
18813a3f90c6SAndrew Thompson {
18823a3f90c6SAndrew Thompson #if (UAUDIO_NCHANBUFS != 2)
18833a3f90c6SAndrew Thompson #error "please update code"
18843a3f90c6SAndrew Thompson #endif
1885a593f6b8SAndrew Thompson 	usbd_transfer_stop(ch->xfer[0]);
1886a593f6b8SAndrew Thompson 	usbd_transfer_stop(ch->xfer[1]);
18873a3f90c6SAndrew Thompson 	return (0);
18883a3f90c6SAndrew Thompson }
18893a3f90c6SAndrew Thompson 
18903a3f90c6SAndrew Thompson /*========================================================================*
18913a3f90c6SAndrew Thompson  * AC - Audio Controller - routines
18923a3f90c6SAndrew Thompson  *========================================================================*/
18933a3f90c6SAndrew Thompson 
18943a3f90c6SAndrew Thompson static void
18953a3f90c6SAndrew Thompson uaudio_mixer_add_ctl_sub(struct uaudio_softc *sc, struct uaudio_mixer_node *mc)
18963a3f90c6SAndrew Thompson {
18973a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *p_mc_new =
18983a3f90c6SAndrew Thompson 	    malloc(sizeof(*p_mc_new), M_USBDEV, M_WAITOK);
18993a3f90c6SAndrew Thompson 
19006f068a43SHans Petter Selasky 	if (p_mc_new != NULL) {
19016f068a43SHans Petter Selasky 		memcpy(p_mc_new, mc, sizeof(*p_mc_new));
19023a3f90c6SAndrew Thompson 		p_mc_new->next = sc->sc_mixer_root;
19033a3f90c6SAndrew Thompson 		sc->sc_mixer_root = p_mc_new;
19043a3f90c6SAndrew Thompson 		sc->sc_mixer_count++;
19053a3f90c6SAndrew Thompson 	} else {
19063a3f90c6SAndrew Thompson 		DPRINTF("out of memory\n");
19073a3f90c6SAndrew Thompson 	}
19083a3f90c6SAndrew Thompson }
19093a3f90c6SAndrew Thompson 
19103a3f90c6SAndrew Thompson static void
19113a3f90c6SAndrew Thompson uaudio_mixer_add_ctl(struct uaudio_softc *sc, struct uaudio_mixer_node *mc)
19123a3f90c6SAndrew Thompson {
19133a3f90c6SAndrew Thompson 	int32_t res;
19143a3f90c6SAndrew Thompson 
19153a3f90c6SAndrew Thompson 	if (mc->class < UAC_NCLASSES) {
19163a3f90c6SAndrew Thompson 		DPRINTF("adding %s.%d\n",
19173a3f90c6SAndrew Thompson 		    uac_names[mc->class], mc->ctl);
19183a3f90c6SAndrew Thompson 	} else {
19193a3f90c6SAndrew Thompson 		DPRINTF("adding %d\n", mc->ctl);
19203a3f90c6SAndrew Thompson 	}
19213a3f90c6SAndrew Thompson 
19223a3f90c6SAndrew Thompson 	if (mc->type == MIX_ON_OFF) {
19233a3f90c6SAndrew Thompson 		mc->minval = 0;
19243a3f90c6SAndrew Thompson 		mc->maxval = 1;
19253a3f90c6SAndrew Thompson 	} else if (mc->type == MIX_SELECTOR) {
19263a3f90c6SAndrew Thompson 	} else {
19273a3f90c6SAndrew Thompson 
19283a3f90c6SAndrew Thompson 		/* determine min and max values */
19293a3f90c6SAndrew Thompson 
1930e2524b2eSHans Petter Selasky 		mc->minval = uaudio_mixer_get(sc->sc_udev,
1931e2524b2eSHans Petter Selasky 		    sc->sc_audio_rev, GET_MIN, mc);
1932e2524b2eSHans Petter Selasky 		mc->maxval = uaudio_mixer_get(sc->sc_udev,
1933e2524b2eSHans Petter Selasky 		    sc->sc_audio_rev, GET_MAX, mc);
19343a3f90c6SAndrew Thompson 
1935b029f6bbSAndrew Thompson 		/* check if max and min was swapped */
19363a3f90c6SAndrew Thompson 
19373a3f90c6SAndrew Thompson 		if (mc->maxval < mc->minval) {
1938b029f6bbSAndrew Thompson 			res = mc->maxval;
19393a3f90c6SAndrew Thompson 			mc->maxval = mc->minval;
1940b029f6bbSAndrew Thompson 			mc->minval = res;
19413a3f90c6SAndrew Thompson 		}
1942b029f6bbSAndrew Thompson 
1943b029f6bbSAndrew Thompson 		/* compute value range */
1944b029f6bbSAndrew Thompson 		mc->mul = mc->maxval - mc->minval;
1945b029f6bbSAndrew Thompson 		if (mc->mul == 0)
1946b029f6bbSAndrew Thompson 			mc->mul = 1;
1947b029f6bbSAndrew Thompson 
1948b029f6bbSAndrew Thompson 		/* compute value alignment */
1949e2524b2eSHans Petter Selasky 		res = uaudio_mixer_get(sc->sc_udev,
1950e2524b2eSHans Petter Selasky 		    sc->sc_audio_rev, GET_RES, mc);
19517fb43570SAndrew Thompson 
19527fb43570SAndrew Thompson 		DPRINTF("Resolution = %d\n", (int)res);
1953b029f6bbSAndrew Thompson 	}
1954b029f6bbSAndrew Thompson 
19553a3f90c6SAndrew Thompson 	uaudio_mixer_add_ctl_sub(sc, mc);
19563a3f90c6SAndrew Thompson 
1957b850ecc1SAndrew Thompson #ifdef USB_DEBUG
19583a3f90c6SAndrew Thompson 	if (uaudio_debug > 2) {
19593a3f90c6SAndrew Thompson 		uint8_t i;
19603a3f90c6SAndrew Thompson 
19613a3f90c6SAndrew Thompson 		for (i = 0; i < mc->nchan; i++) {
19623a3f90c6SAndrew Thompson 			DPRINTF("[mix] wValue=%04x\n", mc->wValue[0]);
19633a3f90c6SAndrew Thompson 		}
19643a3f90c6SAndrew Thompson 		DPRINTF("[mix] wIndex=%04x type=%d ctl='%d' "
19653a3f90c6SAndrew Thompson 		    "min=%d max=%d\n",
19663a3f90c6SAndrew Thompson 		    mc->wIndex, mc->type, mc->ctl,
19673a3f90c6SAndrew Thompson 		    mc->minval, mc->maxval);
19683a3f90c6SAndrew Thompson 	}
19693a3f90c6SAndrew Thompson #endif
19703a3f90c6SAndrew Thompson }
19713a3f90c6SAndrew Thompson 
19723a3f90c6SAndrew Thompson static void
19733a3f90c6SAndrew Thompson uaudio_mixer_add_mixer(struct uaudio_softc *sc,
19743a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
19753a3f90c6SAndrew Thompson {
19763a3f90c6SAndrew Thompson 	struct uaudio_mixer_node mix;
19773a3f90c6SAndrew Thompson 
1978e2524b2eSHans Petter Selasky 	const struct usb_audio_mixer_unit_0 *d0 = iot[id].u.mu_v1;
19794c21be9bSRebecca Cran 	const struct usb_audio_mixer_unit_1 *d1;
19803a3f90c6SAndrew Thompson 
19813a3f90c6SAndrew Thompson 	uint32_t bno;			/* bit number */
19823a3f90c6SAndrew Thompson 	uint32_t p;			/* bit number accumulator */
19833a3f90c6SAndrew Thompson 	uint32_t mo;			/* matching outputs */
19843a3f90c6SAndrew Thompson 	uint32_t mc;			/* matching channels */
19853a3f90c6SAndrew Thompson 	uint32_t ichs;			/* input channels */
19863a3f90c6SAndrew Thompson 	uint32_t ochs;			/* output channels */
19873a3f90c6SAndrew Thompson 	uint32_t c;
19883a3f90c6SAndrew Thompson 	uint32_t chs;			/* channels */
19893a3f90c6SAndrew Thompson 	uint32_t i;
19903a3f90c6SAndrew Thompson 	uint32_t o;
19913a3f90c6SAndrew Thompson 
19923a3f90c6SAndrew Thompson 	DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n",
19933a3f90c6SAndrew Thompson 	    d0->bUnitId, d0->bNrInPins);
19943a3f90c6SAndrew Thompson 
19953a3f90c6SAndrew Thompson 	/* compute the number of input channels */
19963a3f90c6SAndrew Thompson 
19973a3f90c6SAndrew Thompson 	ichs = 0;
19983a3f90c6SAndrew Thompson 	for (i = 0; i < d0->bNrInPins; i++) {
1999e2524b2eSHans Petter Selasky 		ichs += uaudio_mixer_get_cluster(
2000e2524b2eSHans Petter Selasky 		    d0->baSourceId[i], iot).bNrChannels;
20013a3f90c6SAndrew Thompson 	}
20023a3f90c6SAndrew Thompson 
20033a3f90c6SAndrew Thompson 	d1 = (const void *)(d0->baSourceId + d0->bNrInPins);
20043a3f90c6SAndrew Thompson 
20053a3f90c6SAndrew Thompson 	/* and the number of output channels */
20063a3f90c6SAndrew Thompson 
20073a3f90c6SAndrew Thompson 	ochs = d1->bNrChannels;
20083a3f90c6SAndrew Thompson 
20093a3f90c6SAndrew Thompson 	DPRINTFN(3, "ichs=%d ochs=%d\n", ichs, ochs);
20103a3f90c6SAndrew Thompson 
20116f068a43SHans Petter Selasky 	memset(&mix, 0, sizeof(mix));
20123a3f90c6SAndrew Thompson 
20133a3f90c6SAndrew Thompson 	mix.wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no);
20143a3f90c6SAndrew Thompson 	uaudio_mixer_determine_class(&iot[id], &mix);
20153a3f90c6SAndrew Thompson 	mix.type = MIX_SIGNED_16;
20163a3f90c6SAndrew Thompson 
2017e2524b2eSHans Petter Selasky 	if (uaudio_mixer_verify_desc(d0, ((ichs * ochs) + 7) / 8) == NULL)
20183a3f90c6SAndrew Thompson 		return;
2019e2524b2eSHans Petter Selasky 
20203a3f90c6SAndrew Thompson 	for (p = i = 0; i < d0->bNrInPins; i++) {
2021e2524b2eSHans Petter Selasky 		chs = uaudio_mixer_get_cluster(
2022e2524b2eSHans Petter Selasky 		    d0->baSourceId[i], iot).bNrChannels;
20233a3f90c6SAndrew Thompson 		mc = 0;
20243a3f90c6SAndrew Thompson 		for (c = 0; c < chs; c++) {
20253a3f90c6SAndrew Thompson 			mo = 0;
20263a3f90c6SAndrew Thompson 			for (o = 0; o < ochs; o++) {
20273a3f90c6SAndrew Thompson 				bno = ((p + c) * ochs) + o;
2028e2524b2eSHans Petter Selasky 				if (BIT_TEST(d1->bmControls, bno))
20293a3f90c6SAndrew Thompson 					mo++;
20303a3f90c6SAndrew Thompson 			}
2031e2524b2eSHans Petter Selasky 			if (mo == 1)
20323a3f90c6SAndrew Thompson 				mc++;
20333a3f90c6SAndrew Thompson 		}
20343a3f90c6SAndrew Thompson 		if ((mc == chs) && (chs <= MIX_MAX_CHAN)) {
20353a3f90c6SAndrew Thompson 
20363a3f90c6SAndrew Thompson 			/* repeat bit-scan */
20373a3f90c6SAndrew Thompson 
20383a3f90c6SAndrew Thompson 			mc = 0;
20393a3f90c6SAndrew Thompson 			for (c = 0; c < chs; c++) {
20403a3f90c6SAndrew Thompson 				for (o = 0; o < ochs; o++) {
20413a3f90c6SAndrew Thompson 					bno = ((p + c) * ochs) + o;
2042e2524b2eSHans Petter Selasky 					if (BIT_TEST(d1->bmControls, bno))
20433a3f90c6SAndrew Thompson 						mix.wValue[mc++] = MAKE_WORD(p + c + 1, o + 1);
20443a3f90c6SAndrew Thompson 				}
20453a3f90c6SAndrew Thompson 			}
20463a3f90c6SAndrew Thompson 			mix.nchan = chs;
20473a3f90c6SAndrew Thompson 			uaudio_mixer_add_ctl(sc, &mix);
2048e2524b2eSHans Petter Selasky 		}
2049e2524b2eSHans Petter Selasky 		p += chs;
2050e2524b2eSHans Petter Selasky 	}
2051e2524b2eSHans Petter Selasky }
2052e2524b2eSHans Petter Selasky 
2053e2524b2eSHans Petter Selasky static void
2054e2524b2eSHans Petter Selasky uaudio20_mixer_add_mixer(struct uaudio_softc *sc,
2055e2524b2eSHans Petter Selasky     const struct uaudio_terminal_node *iot, int id)
2056e2524b2eSHans Petter Selasky {
2057e2524b2eSHans Petter Selasky 	struct uaudio_mixer_node mix;
2058e2524b2eSHans Petter Selasky 
2059e2524b2eSHans Petter Selasky 	const struct usb_audio20_mixer_unit_0 *d0 = iot[id].u.mu_v2;
2060e2524b2eSHans Petter Selasky 	const struct usb_audio20_mixer_unit_1 *d1;
2061e2524b2eSHans Petter Selasky 
2062e2524b2eSHans Petter Selasky 	uint32_t bno;			/* bit number */
2063e2524b2eSHans Petter Selasky 	uint32_t p;			/* bit number accumulator */
2064e2524b2eSHans Petter Selasky 	uint32_t mo;			/* matching outputs */
2065e2524b2eSHans Petter Selasky 	uint32_t mc;			/* matching channels */
2066e2524b2eSHans Petter Selasky 	uint32_t ichs;			/* input channels */
2067e2524b2eSHans Petter Selasky 	uint32_t ochs;			/* output channels */
2068e2524b2eSHans Petter Selasky 	uint32_t c;
2069e2524b2eSHans Petter Selasky 	uint32_t chs;			/* channels */
2070e2524b2eSHans Petter Selasky 	uint32_t i;
2071e2524b2eSHans Petter Selasky 	uint32_t o;
2072e2524b2eSHans Petter Selasky 
2073e2524b2eSHans Petter Selasky 	DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n",
2074e2524b2eSHans Petter Selasky 	    d0->bUnitId, d0->bNrInPins);
2075e2524b2eSHans Petter Selasky 
2076e2524b2eSHans Petter Selasky 	/* compute the number of input channels */
2077e2524b2eSHans Petter Selasky 
2078e2524b2eSHans Petter Selasky 	ichs = 0;
2079e2524b2eSHans Petter Selasky 	for (i = 0; i < d0->bNrInPins; i++) {
2080e2524b2eSHans Petter Selasky 		ichs += uaudio20_mixer_get_cluster(
2081e2524b2eSHans Petter Selasky 		    d0->baSourceId[i], iot).bNrChannels;
2082e2524b2eSHans Petter Selasky 	}
2083e2524b2eSHans Petter Selasky 
2084e2524b2eSHans Petter Selasky 	d1 = (const void *)(d0->baSourceId + d0->bNrInPins);
2085e2524b2eSHans Petter Selasky 
2086e2524b2eSHans Petter Selasky 	/* and the number of output channels */
2087e2524b2eSHans Petter Selasky 
2088e2524b2eSHans Petter Selasky 	ochs = d1->bNrChannels;
2089e2524b2eSHans Petter Selasky 
2090e2524b2eSHans Petter Selasky 	DPRINTFN(3, "ichs=%d ochs=%d\n", ichs, ochs);
2091e2524b2eSHans Petter Selasky 
2092e2524b2eSHans Petter Selasky 	memset(&mix, 0, sizeof(mix));
2093e2524b2eSHans Petter Selasky 
2094e2524b2eSHans Petter Selasky 	mix.wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no);
2095e2524b2eSHans Petter Selasky 	uaudio20_mixer_determine_class(&iot[id], &mix);
2096e2524b2eSHans Petter Selasky 	mix.type = MIX_SIGNED_16;
2097e2524b2eSHans Petter Selasky 
2098e2524b2eSHans Petter Selasky 	if (uaudio20_mixer_verify_desc(d0, ((ichs * ochs) + 7) / 8) == NULL)
2099e2524b2eSHans Petter Selasky 		return;
2100e2524b2eSHans Petter Selasky 
2101e2524b2eSHans Petter Selasky 	for (p = i = 0; i < d0->bNrInPins; i++) {
2102e2524b2eSHans Petter Selasky 		chs = uaudio20_mixer_get_cluster(
2103e2524b2eSHans Petter Selasky 		    d0->baSourceId[i], iot).bNrChannels;
2104e2524b2eSHans Petter Selasky 		mc = 0;
2105e2524b2eSHans Petter Selasky 		for (c = 0; c < chs; c++) {
2106e2524b2eSHans Petter Selasky 			mo = 0;
2107e2524b2eSHans Petter Selasky 			for (o = 0; o < ochs; o++) {
2108e2524b2eSHans Petter Selasky 				bno = ((p + c) * ochs) + o;
2109e2524b2eSHans Petter Selasky 				if (BIT_TEST(d1->bmControls, bno))
2110e2524b2eSHans Petter Selasky 					mo++;
2111e2524b2eSHans Petter Selasky 			}
2112e2524b2eSHans Petter Selasky 			if (mo == 1)
2113e2524b2eSHans Petter Selasky 				mc++;
2114e2524b2eSHans Petter Selasky 		}
2115e2524b2eSHans Petter Selasky 		if ((mc == chs) && (chs <= MIX_MAX_CHAN)) {
2116e2524b2eSHans Petter Selasky 
2117e2524b2eSHans Petter Selasky 			/* repeat bit-scan */
2118e2524b2eSHans Petter Selasky 
2119e2524b2eSHans Petter Selasky 			mc = 0;
2120e2524b2eSHans Petter Selasky 			for (c = 0; c < chs; c++) {
2121e2524b2eSHans Petter Selasky 				for (o = 0; o < ochs; o++) {
2122e2524b2eSHans Petter Selasky 					bno = ((p + c) * ochs) + o;
2123e2524b2eSHans Petter Selasky 					if (BIT_TEST(d1->bmControls, bno))
2124e2524b2eSHans Petter Selasky 						mix.wValue[mc++] = MAKE_WORD(p + c + 1, o + 1);
2125e2524b2eSHans Petter Selasky 				}
2126e2524b2eSHans Petter Selasky 			}
2127e2524b2eSHans Petter Selasky 			mix.nchan = chs;
2128e2524b2eSHans Petter Selasky 			uaudio_mixer_add_ctl(sc, &mix);
21293a3f90c6SAndrew Thompson 		}
21303a3f90c6SAndrew Thompson 		p += chs;
21313a3f90c6SAndrew Thompson 	}
21323a3f90c6SAndrew Thompson }
21333a3f90c6SAndrew Thompson 
21343a3f90c6SAndrew Thompson static void
21353a3f90c6SAndrew Thompson uaudio_mixer_add_selector(struct uaudio_softc *sc,
21363a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
21373a3f90c6SAndrew Thompson {
2138e2524b2eSHans Petter Selasky 	const struct usb_audio_selector_unit *d = iot[id].u.su_v1;
21393a3f90c6SAndrew Thompson 	struct uaudio_mixer_node mix;
21403a3f90c6SAndrew Thompson 	uint16_t i;
21413a3f90c6SAndrew Thompson 
21423a3f90c6SAndrew Thompson 	DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n",
21433a3f90c6SAndrew Thompson 	    d->bUnitId, d->bNrInPins);
21443a3f90c6SAndrew Thompson 
21453a3f90c6SAndrew Thompson 	if (d->bNrInPins == 0) {
21463a3f90c6SAndrew Thompson 		return;
21473a3f90c6SAndrew Thompson 	}
21486f068a43SHans Petter Selasky 	memset(&mix, 0, sizeof(mix));
21493a3f90c6SAndrew Thompson 
21503a3f90c6SAndrew Thompson 	mix.wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no);
21513a3f90c6SAndrew Thompson 	mix.wValue[0] = MAKE_WORD(0, 0);
21523a3f90c6SAndrew Thompson 	uaudio_mixer_determine_class(&iot[id], &mix);
21533a3f90c6SAndrew Thompson 	mix.nchan = 1;
21543a3f90c6SAndrew Thompson 	mix.type = MIX_SELECTOR;
21553a3f90c6SAndrew Thompson 
21563a3f90c6SAndrew Thompson 	mix.ctl = SOUND_MIXER_NRDEVICES;
21573a3f90c6SAndrew Thompson 	mix.minval = 1;
21583a3f90c6SAndrew Thompson 	mix.maxval = d->bNrInPins;
21593a3f90c6SAndrew Thompson 
21603a3f90c6SAndrew Thompson 	if (mix.maxval > MAX_SELECTOR_INPUT_PIN) {
21613a3f90c6SAndrew Thompson 		mix.maxval = MAX_SELECTOR_INPUT_PIN;
21623a3f90c6SAndrew Thompson 	}
21633a3f90c6SAndrew Thompson 	mix.mul = (mix.maxval - mix.minval);
21643a3f90c6SAndrew Thompson 	for (i = 0; i < MAX_SELECTOR_INPUT_PIN; i++) {
21653a3f90c6SAndrew Thompson 		mix.slctrtype[i] = SOUND_MIXER_NRDEVICES;
21663a3f90c6SAndrew Thompson 	}
21673a3f90c6SAndrew Thompson 
21683a3f90c6SAndrew Thompson 	for (i = 0; i < mix.maxval; i++) {
2169e2524b2eSHans Petter Selasky 		mix.slctrtype[i] = uaudio_mixer_feature_name(
2170e2524b2eSHans Petter Selasky 		    &iot[d->baSourceId[i]], &mix);
2171e2524b2eSHans Petter Selasky 	}
2172e2524b2eSHans Petter Selasky 
2173e2524b2eSHans Petter Selasky 	mix.class = 0;			/* not used */
2174e2524b2eSHans Petter Selasky 
2175e2524b2eSHans Petter Selasky 	uaudio_mixer_add_ctl(sc, &mix);
2176e2524b2eSHans Petter Selasky }
2177e2524b2eSHans Petter Selasky 
2178e2524b2eSHans Petter Selasky static void
2179e2524b2eSHans Petter Selasky uaudio20_mixer_add_selector(struct uaudio_softc *sc,
2180e2524b2eSHans Petter Selasky     const struct uaudio_terminal_node *iot, int id)
2181e2524b2eSHans Petter Selasky {
2182e2524b2eSHans Petter Selasky 	const struct usb_audio20_selector_unit *d = iot[id].u.su_v2;
2183e2524b2eSHans Petter Selasky 	struct uaudio_mixer_node mix;
2184e2524b2eSHans Petter Selasky 	uint16_t i;
2185e2524b2eSHans Petter Selasky 
2186e2524b2eSHans Petter Selasky 	DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n",
2187e2524b2eSHans Petter Selasky 	    d->bUnitId, d->bNrInPins);
2188e2524b2eSHans Petter Selasky 
2189e2524b2eSHans Petter Selasky 	if (d->bNrInPins == 0)
2190e2524b2eSHans Petter Selasky 		return;
2191e2524b2eSHans Petter Selasky 
2192e2524b2eSHans Petter Selasky 	memset(&mix, 0, sizeof(mix));
2193e2524b2eSHans Petter Selasky 
2194e2524b2eSHans Petter Selasky 	mix.wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no);
2195e2524b2eSHans Petter Selasky 	mix.wValue[0] = MAKE_WORD(0, 0);
2196e2524b2eSHans Petter Selasky 	uaudio20_mixer_determine_class(&iot[id], &mix);
2197e2524b2eSHans Petter Selasky 	mix.nchan = 1;
2198e2524b2eSHans Petter Selasky 	mix.type = MIX_SELECTOR;
2199e2524b2eSHans Petter Selasky 
2200e2524b2eSHans Petter Selasky 	mix.ctl = SOUND_MIXER_NRDEVICES;
2201e2524b2eSHans Petter Selasky 	mix.minval = 1;
2202e2524b2eSHans Petter Selasky 	mix.maxval = d->bNrInPins;
2203e2524b2eSHans Petter Selasky 
2204e2524b2eSHans Petter Selasky 	if (mix.maxval > MAX_SELECTOR_INPUT_PIN)
2205e2524b2eSHans Petter Selasky 		mix.maxval = MAX_SELECTOR_INPUT_PIN;
2206e2524b2eSHans Petter Selasky 
2207e2524b2eSHans Petter Selasky 	mix.mul = (mix.maxval - mix.minval);
2208e2524b2eSHans Petter Selasky 	for (i = 0; i < MAX_SELECTOR_INPUT_PIN; i++)
2209e2524b2eSHans Petter Selasky 		mix.slctrtype[i] = SOUND_MIXER_NRDEVICES;
2210e2524b2eSHans Petter Selasky 
2211e2524b2eSHans Petter Selasky 	for (i = 0; i < mix.maxval; i++) {
2212e2524b2eSHans Petter Selasky 		mix.slctrtype[i] = uaudio20_mixer_feature_name(
2213e2524b2eSHans Petter Selasky 		    &iot[d->baSourceId[i]], &mix);
22143a3f90c6SAndrew Thompson 	}
22153a3f90c6SAndrew Thompson 
22163a3f90c6SAndrew Thompson 	mix.class = 0;			/* not used */
22173a3f90c6SAndrew Thompson 
22183a3f90c6SAndrew Thompson 	uaudio_mixer_add_ctl(sc, &mix);
22193a3f90c6SAndrew Thompson }
22203a3f90c6SAndrew Thompson 
22213a3f90c6SAndrew Thompson static uint32_t
22224c21be9bSRebecca Cran uaudio_mixer_feature_get_bmaControls(const struct usb_audio_feature_unit *d,
22236d917491SHans Petter Selasky     uint8_t i)
22243a3f90c6SAndrew Thompson {
22253a3f90c6SAndrew Thompson 	uint32_t temp = 0;
22266d917491SHans Petter Selasky 	uint32_t offset = (i * d->bControlSize);
22273a3f90c6SAndrew Thompson 
22283a3f90c6SAndrew Thompson 	if (d->bControlSize > 0) {
22293a3f90c6SAndrew Thompson 		temp |= d->bmaControls[offset];
22303a3f90c6SAndrew Thompson 		if (d->bControlSize > 1) {
22313a3f90c6SAndrew Thompson 			temp |= d->bmaControls[offset + 1] << 8;
22323a3f90c6SAndrew Thompson 			if (d->bControlSize > 2) {
22333a3f90c6SAndrew Thompson 				temp |= d->bmaControls[offset + 2] << 16;
22343a3f90c6SAndrew Thompson 				if (d->bControlSize > 3) {
22353a3f90c6SAndrew Thompson 					temp |= d->bmaControls[offset + 3] << 24;
22363a3f90c6SAndrew Thompson 				}
22373a3f90c6SAndrew Thompson 			}
22383a3f90c6SAndrew Thompson 		}
22393a3f90c6SAndrew Thompson 	}
22403a3f90c6SAndrew Thompson 	return (temp);
22413a3f90c6SAndrew Thompson }
22423a3f90c6SAndrew Thompson 
22433a3f90c6SAndrew Thompson static void
22443a3f90c6SAndrew Thompson uaudio_mixer_add_feature(struct uaudio_softc *sc,
22453a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
22463a3f90c6SAndrew Thompson {
2247e2524b2eSHans Petter Selasky 	const struct usb_audio_feature_unit *d = iot[id].u.fu_v1;
22483a3f90c6SAndrew Thompson 	struct uaudio_mixer_node mix;
22493a3f90c6SAndrew Thompson 	uint32_t fumask;
22503a3f90c6SAndrew Thompson 	uint32_t mmask;
22513a3f90c6SAndrew Thompson 	uint32_t cmask;
22523a3f90c6SAndrew Thompson 	uint16_t mixernumber;
22533a3f90c6SAndrew Thompson 	uint8_t nchan;
22543a3f90c6SAndrew Thompson 	uint8_t chan;
22553a3f90c6SAndrew Thompson 	uint8_t ctl;
22563a3f90c6SAndrew Thompson 	uint8_t i;
22573a3f90c6SAndrew Thompson 
22583a3f90c6SAndrew Thompson 	if (d->bControlSize == 0) {
22593a3f90c6SAndrew Thompson 		return;
22603a3f90c6SAndrew Thompson 	}
22616f068a43SHans Petter Selasky 	memset(&mix, 0, sizeof(mix));
22623a3f90c6SAndrew Thompson 
22633a3f90c6SAndrew Thompson 	nchan = (d->bLength - 7) / d->bControlSize;
22643a3f90c6SAndrew Thompson 	mmask = uaudio_mixer_feature_get_bmaControls(d, 0);
22653a3f90c6SAndrew Thompson 	cmask = 0;
22663a3f90c6SAndrew Thompson 
22673a3f90c6SAndrew Thompson 	if (nchan == 0) {
22683a3f90c6SAndrew Thompson 		return;
22693a3f90c6SAndrew Thompson 	}
22703a3f90c6SAndrew Thompson 	/* figure out what we can control */
22713a3f90c6SAndrew Thompson 
22723a3f90c6SAndrew Thompson 	for (chan = 1; chan < nchan; chan++) {
22733a3f90c6SAndrew Thompson 		DPRINTFN(10, "chan=%d mask=%x\n",
22743a3f90c6SAndrew Thompson 		    chan, uaudio_mixer_feature_get_bmaControls(d, chan));
22753a3f90c6SAndrew Thompson 
22763a3f90c6SAndrew Thompson 		cmask |= uaudio_mixer_feature_get_bmaControls(d, chan);
22773a3f90c6SAndrew Thompson 	}
22783a3f90c6SAndrew Thompson 
22793a3f90c6SAndrew Thompson 	if (nchan > MIX_MAX_CHAN) {
22803a3f90c6SAndrew Thompson 		nchan = MIX_MAX_CHAN;
22813a3f90c6SAndrew Thompson 	}
22823a3f90c6SAndrew Thompson 	mix.wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no);
22833a3f90c6SAndrew Thompson 
22843a3f90c6SAndrew Thompson 	for (ctl = 1; ctl <= LOUDNESS_CONTROL; ctl++) {
22853a3f90c6SAndrew Thompson 
22863a3f90c6SAndrew Thompson 		fumask = FU_MASK(ctl);
22873a3f90c6SAndrew Thompson 
22883a3f90c6SAndrew Thompson 		DPRINTFN(5, "ctl=%d fumask=0x%04x\n",
22893a3f90c6SAndrew Thompson 		    ctl, fumask);
22903a3f90c6SAndrew Thompson 
22913a3f90c6SAndrew Thompson 		if (mmask & fumask) {
22923a3f90c6SAndrew Thompson 			mix.nchan = 1;
22933a3f90c6SAndrew Thompson 			mix.wValue[0] = MAKE_WORD(ctl, 0);
22943a3f90c6SAndrew Thompson 		} else if (cmask & fumask) {
22953a3f90c6SAndrew Thompson 			mix.nchan = nchan - 1;
22963a3f90c6SAndrew Thompson 			for (i = 1; i < nchan; i++) {
22973a3f90c6SAndrew Thompson 				if (uaudio_mixer_feature_get_bmaControls(d, i) & fumask)
22983a3f90c6SAndrew Thompson 					mix.wValue[i - 1] = MAKE_WORD(ctl, i);
22993a3f90c6SAndrew Thompson 				else
23003a3f90c6SAndrew Thompson 					mix.wValue[i - 1] = -1;
23013a3f90c6SAndrew Thompson 			}
23023a3f90c6SAndrew Thompson 		} else {
23033a3f90c6SAndrew Thompson 			continue;
23043a3f90c6SAndrew Thompson 		}
23053a3f90c6SAndrew Thompson 
23063a3f90c6SAndrew Thompson 		mixernumber = uaudio_mixer_feature_name(&iot[id], &mix);
23073a3f90c6SAndrew Thompson 
23083a3f90c6SAndrew Thompson 		switch (ctl) {
23093a3f90c6SAndrew Thompson 		case MUTE_CONTROL:
23103a3f90c6SAndrew Thompson 			mix.type = MIX_ON_OFF;
23113a3f90c6SAndrew Thompson 			mix.ctl = SOUND_MIXER_NRDEVICES;
23123a3f90c6SAndrew Thompson 			break;
23133a3f90c6SAndrew Thompson 
23143a3f90c6SAndrew Thompson 		case VOLUME_CONTROL:
23153a3f90c6SAndrew Thompson 			mix.type = MIX_SIGNED_16;
23163a3f90c6SAndrew Thompson 			mix.ctl = mixernumber;
23173a3f90c6SAndrew Thompson 			break;
23183a3f90c6SAndrew Thompson 
23193a3f90c6SAndrew Thompson 		case BASS_CONTROL:
23203a3f90c6SAndrew Thompson 			mix.type = MIX_SIGNED_8;
23213a3f90c6SAndrew Thompson 			mix.ctl = SOUND_MIXER_BASS;
23223a3f90c6SAndrew Thompson 			break;
23233a3f90c6SAndrew Thompson 
23243a3f90c6SAndrew Thompson 		case MID_CONTROL:
23253a3f90c6SAndrew Thompson 			mix.type = MIX_SIGNED_8;
23263a3f90c6SAndrew Thompson 			mix.ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
23273a3f90c6SAndrew Thompson 			break;
23283a3f90c6SAndrew Thompson 
23293a3f90c6SAndrew Thompson 		case TREBLE_CONTROL:
23303a3f90c6SAndrew Thompson 			mix.type = MIX_SIGNED_8;
23313a3f90c6SAndrew Thompson 			mix.ctl = SOUND_MIXER_TREBLE;
23323a3f90c6SAndrew Thompson 			break;
23333a3f90c6SAndrew Thompson 
23343a3f90c6SAndrew Thompson 		case GRAPHIC_EQUALIZER_CONTROL:
23353a3f90c6SAndrew Thompson 			continue;	/* XXX don't add anything */
23363a3f90c6SAndrew Thompson 			break;
23373a3f90c6SAndrew Thompson 
23383a3f90c6SAndrew Thompson 		case AGC_CONTROL:
23393a3f90c6SAndrew Thompson 			mix.type = MIX_ON_OFF;
23403a3f90c6SAndrew Thompson 			mix.ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
23413a3f90c6SAndrew Thompson 			break;
23423a3f90c6SAndrew Thompson 
23433a3f90c6SAndrew Thompson 		case DELAY_CONTROL:
23443a3f90c6SAndrew Thompson 			mix.type = MIX_UNSIGNED_16;
23453a3f90c6SAndrew Thompson 			mix.ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
23463a3f90c6SAndrew Thompson 			break;
23473a3f90c6SAndrew Thompson 
23483a3f90c6SAndrew Thompson 		case BASS_BOOST_CONTROL:
23493a3f90c6SAndrew Thompson 			mix.type = MIX_ON_OFF;
23503a3f90c6SAndrew Thompson 			mix.ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
23513a3f90c6SAndrew Thompson 			break;
23523a3f90c6SAndrew Thompson 
23533a3f90c6SAndrew Thompson 		case LOUDNESS_CONTROL:
23543a3f90c6SAndrew Thompson 			mix.type = MIX_ON_OFF;
23553a3f90c6SAndrew Thompson 			mix.ctl = SOUND_MIXER_LOUD;	/* Is this correct ? */
23563a3f90c6SAndrew Thompson 			break;
23573a3f90c6SAndrew Thompson 
23583a3f90c6SAndrew Thompson 		default:
23593a3f90c6SAndrew Thompson 			mix.type = MIX_UNKNOWN;
23603a3f90c6SAndrew Thompson 			break;
23613a3f90c6SAndrew Thompson 		}
23623a3f90c6SAndrew Thompson 
2363e2524b2eSHans Petter Selasky 		if (mix.type != MIX_UNKNOWN)
23643a3f90c6SAndrew Thompson 			uaudio_mixer_add_ctl(sc, &mix);
23653a3f90c6SAndrew Thompson 	}
23663a3f90c6SAndrew Thompson }
2367e2524b2eSHans Petter Selasky 
2368e2524b2eSHans Petter Selasky static void
2369e2524b2eSHans Petter Selasky uaudio20_mixer_add_feature(struct uaudio_softc *sc,
2370e2524b2eSHans Petter Selasky     const struct uaudio_terminal_node *iot, int id)
2371e2524b2eSHans Petter Selasky {
2372e2524b2eSHans Petter Selasky 	const struct usb_audio20_feature_unit *d = iot[id].u.fu_v2;
2373e2524b2eSHans Petter Selasky 	struct uaudio_mixer_node mix;
2374e2524b2eSHans Petter Selasky 	uint32_t ctl;
2375e2524b2eSHans Petter Selasky 	uint32_t mmask;
2376e2524b2eSHans Petter Selasky 	uint32_t cmask;
2377e2524b2eSHans Petter Selasky 	uint16_t mixernumber;
2378e2524b2eSHans Petter Selasky 	uint8_t nchan;
2379e2524b2eSHans Petter Selasky 	uint8_t chan;
2380e2524b2eSHans Petter Selasky 	uint8_t i;
2381e2524b2eSHans Petter Selasky 	uint8_t what;
2382e2524b2eSHans Petter Selasky 
2383e2524b2eSHans Petter Selasky 	if (UGETDW(d->bmaControls[0]) == 0)
2384e2524b2eSHans Petter Selasky 		return;
2385e2524b2eSHans Petter Selasky 
2386e2524b2eSHans Petter Selasky 	memset(&mix, 0, sizeof(mix));
2387e2524b2eSHans Petter Selasky 
2388e2524b2eSHans Petter Selasky 	nchan = (d->bLength - 6) / 4;
2389e2524b2eSHans Petter Selasky 	mmask = UGETDW(d->bmaControls[0]);
2390e2524b2eSHans Petter Selasky 	cmask = 0;
2391e2524b2eSHans Petter Selasky 
2392e2524b2eSHans Petter Selasky 	if (nchan == 0)
2393e2524b2eSHans Petter Selasky 		return;
2394e2524b2eSHans Petter Selasky 
2395e2524b2eSHans Petter Selasky 	/* figure out what we can control */
2396e2524b2eSHans Petter Selasky 
2397e2524b2eSHans Petter Selasky 	for (chan = 1; chan < nchan; chan++)
2398e2524b2eSHans Petter Selasky 		cmask |= UGETDW(d->bmaControls[chan]);
2399e2524b2eSHans Petter Selasky 
2400e2524b2eSHans Petter Selasky 	if (nchan > MIX_MAX_CHAN)
2401e2524b2eSHans Petter Selasky 		nchan = MIX_MAX_CHAN;
2402e2524b2eSHans Petter Selasky 
2403e2524b2eSHans Petter Selasky 	mix.wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no);
2404e2524b2eSHans Petter Selasky 
2405e2524b2eSHans Petter Selasky 	for (ctl = 3; ctl != 0; ctl <<= 2) {
2406e2524b2eSHans Petter Selasky 
2407e2524b2eSHans Petter Selasky 		mixernumber = uaudio20_mixer_feature_name(&iot[id], &mix);
2408e2524b2eSHans Petter Selasky 
2409e2524b2eSHans Petter Selasky 		switch (ctl) {
2410e2524b2eSHans Petter Selasky 		case (3 << 0):
2411e2524b2eSHans Petter Selasky 			mix.type = MIX_ON_OFF;
2412e2524b2eSHans Petter Selasky 			mix.ctl = SOUND_MIXER_NRDEVICES;
2413e2524b2eSHans Petter Selasky 			what = MUTE_CONTROL;
2414e2524b2eSHans Petter Selasky 			break;
2415e2524b2eSHans Petter Selasky 		case (3 << 2):
2416e2524b2eSHans Petter Selasky 			mix.type = MIX_SIGNED_16;
2417e2524b2eSHans Petter Selasky 			mix.ctl = mixernumber;
2418e2524b2eSHans Petter Selasky 			what = VOLUME_CONTROL;
2419e2524b2eSHans Petter Selasky 			break;
2420e2524b2eSHans Petter Selasky 		case (3 << 4):
2421e2524b2eSHans Petter Selasky 			mix.type = MIX_SIGNED_8;
2422e2524b2eSHans Petter Selasky 			mix.ctl = SOUND_MIXER_BASS;
2423e2524b2eSHans Petter Selasky 			what = BASS_CONTROL;
2424e2524b2eSHans Petter Selasky 			break;
2425e2524b2eSHans Petter Selasky 		case (3 << 6):
2426e2524b2eSHans Petter Selasky 			mix.type = MIX_SIGNED_8;
2427e2524b2eSHans Petter Selasky 			mix.ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
2428e2524b2eSHans Petter Selasky 			what = MID_CONTROL;
2429e2524b2eSHans Petter Selasky 			break;
2430e2524b2eSHans Petter Selasky 		case (3 << 8):
2431e2524b2eSHans Petter Selasky 			mix.type = MIX_SIGNED_8;
2432e2524b2eSHans Petter Selasky 			mix.ctl = SOUND_MIXER_TREBLE;
2433e2524b2eSHans Petter Selasky 			what = TREBLE_CONTROL;
2434e2524b2eSHans Petter Selasky 			break;
2435e2524b2eSHans Petter Selasky 		case (3 << 12):
2436e2524b2eSHans Petter Selasky 			mix.type = MIX_ON_OFF;
2437e2524b2eSHans Petter Selasky 			mix.ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
2438e2524b2eSHans Petter Selasky 			what = AGC_CONTROL;
2439e2524b2eSHans Petter Selasky 			break;
2440e2524b2eSHans Petter Selasky 		case (3 << 14):
2441e2524b2eSHans Petter Selasky 			mix.type = MIX_UNSIGNED_16;
2442e2524b2eSHans Petter Selasky 			mix.ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
2443e2524b2eSHans Petter Selasky 			what = DELAY_CONTROL;
2444e2524b2eSHans Petter Selasky 			break;
2445e2524b2eSHans Petter Selasky 		case (3 << 16):
2446e2524b2eSHans Petter Selasky 			mix.type = MIX_ON_OFF;
2447e2524b2eSHans Petter Selasky 			mix.ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
2448e2524b2eSHans Petter Selasky 			what = BASS_BOOST_CONTROL;
2449e2524b2eSHans Petter Selasky 			break;
2450e2524b2eSHans Petter Selasky 		case (3 << 18):
2451e2524b2eSHans Petter Selasky 			mix.type = MIX_ON_OFF;
2452e2524b2eSHans Petter Selasky 			mix.ctl = SOUND_MIXER_LOUD;	/* Is this correct ? */
2453e2524b2eSHans Petter Selasky 			what = LOUDNESS_CONTROL;
2454e2524b2eSHans Petter Selasky 			break;
2455e2524b2eSHans Petter Selasky 		case (3 << 20):
2456e2524b2eSHans Petter Selasky 			mix.type = MIX_SIGNED_16;
2457e2524b2eSHans Petter Selasky 			mix.ctl = mixernumber;
2458e2524b2eSHans Petter Selasky 			what = INPUT_GAIN_CONTROL;
2459e2524b2eSHans Petter Selasky 			break;
2460e2524b2eSHans Petter Selasky 		case (3 << 22):
2461e2524b2eSHans Petter Selasky 			mix.type = MIX_SIGNED_16;
2462e2524b2eSHans Petter Selasky 			mix.ctl = mixernumber;
2463e2524b2eSHans Petter Selasky 			what = INPUT_GAIN_PAD_CONTROL;
2464e2524b2eSHans Petter Selasky 			break;
2465e2524b2eSHans Petter Selasky 		default:
2466e2524b2eSHans Petter Selasky 			continue;
2467e2524b2eSHans Petter Selasky 		}
2468e2524b2eSHans Petter Selasky 
2469e2524b2eSHans Petter Selasky 		if ((mmask & ctl) == ctl) {
2470e2524b2eSHans Petter Selasky 			mix.nchan = 1;
2471e2524b2eSHans Petter Selasky 			mix.wValue[0] = MAKE_WORD(what, 0);
2472e2524b2eSHans Petter Selasky 		} else if ((cmask & ctl) == ctl) {
2473e2524b2eSHans Petter Selasky 			mix.nchan = nchan - 1;
2474e2524b2eSHans Petter Selasky 			for (i = 1; i < nchan; i++) {
2475e2524b2eSHans Petter Selasky 				if ((UGETDW(d->bmaControls[i]) & ctl) == ctl)
2476e2524b2eSHans Petter Selasky 					mix.wValue[i - 1] = MAKE_WORD(what, i);
2477e2524b2eSHans Petter Selasky 				else
2478e2524b2eSHans Petter Selasky 					mix.wValue[i - 1] = -1;
2479e2524b2eSHans Petter Selasky 			}
2480e2524b2eSHans Petter Selasky 		} else {
2481e2524b2eSHans Petter Selasky 			continue;
2482e2524b2eSHans Petter Selasky 		}
2483e2524b2eSHans Petter Selasky 
2484e2524b2eSHans Petter Selasky 		if (mix.type != MIX_UNKNOWN)
2485e2524b2eSHans Petter Selasky 			uaudio_mixer_add_ctl(sc, &mix);
2486e2524b2eSHans Petter Selasky 	}
24873a3f90c6SAndrew Thompson }
24883a3f90c6SAndrew Thompson 
24893a3f90c6SAndrew Thompson static void
24903a3f90c6SAndrew Thompson uaudio_mixer_add_processing_updown(struct uaudio_softc *sc,
24913a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
24923a3f90c6SAndrew Thompson {
2493e2524b2eSHans Petter Selasky 	const struct usb_audio_processing_unit_0 *d0 = iot[id].u.pu_v1;
24944c21be9bSRebecca Cran 	const struct usb_audio_processing_unit_1 *d1 =
24953a3f90c6SAndrew Thompson 	(const void *)(d0->baSourceId + d0->bNrInPins);
24964c21be9bSRebecca Cran 	const struct usb_audio_processing_unit_updown *ud =
24973a3f90c6SAndrew Thompson 	(const void *)(d1->bmControls + d1->bControlSize);
24983a3f90c6SAndrew Thompson 	struct uaudio_mixer_node mix;
24993a3f90c6SAndrew Thompson 	uint8_t i;
25003a3f90c6SAndrew Thompson 
25013a3f90c6SAndrew Thompson 	if (uaudio_mixer_verify_desc(d0, sizeof(*ud)) == NULL) {
25023a3f90c6SAndrew Thompson 		return;
25033a3f90c6SAndrew Thompson 	}
25043a3f90c6SAndrew Thompson 	if (uaudio_mixer_verify_desc(d0, sizeof(*ud) + (2 * ud->bNrModes))
25053a3f90c6SAndrew Thompson 	    == NULL) {
25063a3f90c6SAndrew Thompson 		return;
25073a3f90c6SAndrew Thompson 	}
25083a3f90c6SAndrew Thompson 	DPRINTFN(3, "bUnitId=%d bNrModes=%d\n",
25093a3f90c6SAndrew Thompson 	    d0->bUnitId, ud->bNrModes);
25103a3f90c6SAndrew Thompson 
25113a3f90c6SAndrew Thompson 	if (!(d1->bmControls[0] & UA_PROC_MASK(UD_MODE_SELECT_CONTROL))) {
25123a3f90c6SAndrew Thompson 		DPRINTF("no mode select\n");
25133a3f90c6SAndrew Thompson 		return;
25143a3f90c6SAndrew Thompson 	}
25156f068a43SHans Petter Selasky 	memset(&mix, 0, sizeof(mix));
25163a3f90c6SAndrew Thompson 
25173a3f90c6SAndrew Thompson 	mix.wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no);
25183a3f90c6SAndrew Thompson 	mix.nchan = 1;
25193a3f90c6SAndrew Thompson 	mix.wValue[0] = MAKE_WORD(UD_MODE_SELECT_CONTROL, 0);
25203a3f90c6SAndrew Thompson 	uaudio_mixer_determine_class(&iot[id], &mix);
25213a3f90c6SAndrew Thompson 	mix.type = MIX_ON_OFF;		/* XXX */
25223a3f90c6SAndrew Thompson 
25233a3f90c6SAndrew Thompson 	for (i = 0; i < ud->bNrModes; i++) {
25243a3f90c6SAndrew Thompson 		DPRINTFN(3, "i=%d bm=0x%x\n", i, UGETW(ud->waModes[i]));
25253a3f90c6SAndrew Thompson 		/* XXX */
25263a3f90c6SAndrew Thompson 	}
25273a3f90c6SAndrew Thompson 
25283a3f90c6SAndrew Thompson 	uaudio_mixer_add_ctl(sc, &mix);
25293a3f90c6SAndrew Thompson }
25303a3f90c6SAndrew Thompson 
25313a3f90c6SAndrew Thompson static void
25323a3f90c6SAndrew Thompson uaudio_mixer_add_processing(struct uaudio_softc *sc,
25333a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
25343a3f90c6SAndrew Thompson {
2535e2524b2eSHans Petter Selasky 	const struct usb_audio_processing_unit_0 *d0 = iot[id].u.pu_v1;
25364c21be9bSRebecca Cran 	const struct usb_audio_processing_unit_1 *d1 =
25373a3f90c6SAndrew Thompson 	(const void *)(d0->baSourceId + d0->bNrInPins);
25383a3f90c6SAndrew Thompson 	struct uaudio_mixer_node mix;
25393a3f90c6SAndrew Thompson 	uint16_t ptype;
25403a3f90c6SAndrew Thompson 
25416f068a43SHans Petter Selasky 	memset(&mix, 0, sizeof(mix));
25423a3f90c6SAndrew Thompson 
25433a3f90c6SAndrew Thompson 	ptype = UGETW(d0->wProcessType);
25443a3f90c6SAndrew Thompson 
25453a3f90c6SAndrew Thompson 	DPRINTFN(3, "wProcessType=%d bUnitId=%d "
25463a3f90c6SAndrew Thompson 	    "bNrInPins=%d\n", ptype, d0->bUnitId, d0->bNrInPins);
25473a3f90c6SAndrew Thompson 
25483a3f90c6SAndrew Thompson 	if (d1->bControlSize == 0) {
25493a3f90c6SAndrew Thompson 		return;
25503a3f90c6SAndrew Thompson 	}
25513a3f90c6SAndrew Thompson 	if (d1->bmControls[0] & UA_PROC_ENABLE_MASK) {
25523a3f90c6SAndrew Thompson 		mix.wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no);
25533a3f90c6SAndrew Thompson 		mix.nchan = 1;
25543a3f90c6SAndrew Thompson 		mix.wValue[0] = MAKE_WORD(XX_ENABLE_CONTROL, 0);
25553a3f90c6SAndrew Thompson 		uaudio_mixer_determine_class(&iot[id], &mix);
25563a3f90c6SAndrew Thompson 		mix.type = MIX_ON_OFF;
25573a3f90c6SAndrew Thompson 		uaudio_mixer_add_ctl(sc, &mix);
25583a3f90c6SAndrew Thompson 	}
25593a3f90c6SAndrew Thompson 	switch (ptype) {
25603a3f90c6SAndrew Thompson 	case UPDOWNMIX_PROCESS:
25613a3f90c6SAndrew Thompson 		uaudio_mixer_add_processing_updown(sc, iot, id);
25623a3f90c6SAndrew Thompson 		break;
25633a3f90c6SAndrew Thompson 
25643a3f90c6SAndrew Thompson 	case DOLBY_PROLOGIC_PROCESS:
25653a3f90c6SAndrew Thompson 	case P3D_STEREO_EXTENDER_PROCESS:
25663a3f90c6SAndrew Thompson 	case REVERBATION_PROCESS:
25673a3f90c6SAndrew Thompson 	case CHORUS_PROCESS:
25683a3f90c6SAndrew Thompson 	case DYN_RANGE_COMP_PROCESS:
25693a3f90c6SAndrew Thompson 	default:
25703a3f90c6SAndrew Thompson 		DPRINTF("unit %d, type=%d is not implemented\n",
25713a3f90c6SAndrew Thompson 		    d0->bUnitId, ptype);
25723a3f90c6SAndrew Thompson 		break;
25733a3f90c6SAndrew Thompson 	}
25743a3f90c6SAndrew Thompson }
25753a3f90c6SAndrew Thompson 
25763a3f90c6SAndrew Thompson static void
25773a3f90c6SAndrew Thompson uaudio_mixer_add_extension(struct uaudio_softc *sc,
25783a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
25793a3f90c6SAndrew Thompson {
2580e2524b2eSHans Petter Selasky 	const struct usb_audio_extension_unit_0 *d0 = iot[id].u.eu_v1;
25814c21be9bSRebecca Cran 	const struct usb_audio_extension_unit_1 *d1 =
25823a3f90c6SAndrew Thompson 	(const void *)(d0->baSourceId + d0->bNrInPins);
25833a3f90c6SAndrew Thompson 	struct uaudio_mixer_node mix;
25843a3f90c6SAndrew Thompson 
25853a3f90c6SAndrew Thompson 	DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n",
25863a3f90c6SAndrew Thompson 	    d0->bUnitId, d0->bNrInPins);
25873a3f90c6SAndrew Thompson 
25883a3f90c6SAndrew Thompson 	if (sc->sc_uq_au_no_xu) {
25893a3f90c6SAndrew Thompson 		return;
25903a3f90c6SAndrew Thompson 	}
25913a3f90c6SAndrew Thompson 	if (d1->bControlSize == 0) {
25923a3f90c6SAndrew Thompson 		return;
25933a3f90c6SAndrew Thompson 	}
25943a3f90c6SAndrew Thompson 	if (d1->bmControls[0] & UA_EXT_ENABLE_MASK) {
25953a3f90c6SAndrew Thompson 
25966f068a43SHans Petter Selasky 		memset(&mix, 0, sizeof(mix));
25973a3f90c6SAndrew Thompson 
25983a3f90c6SAndrew Thompson 		mix.wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no);
25993a3f90c6SAndrew Thompson 		mix.nchan = 1;
26003a3f90c6SAndrew Thompson 		mix.wValue[0] = MAKE_WORD(UA_EXT_ENABLE, 0);
26013a3f90c6SAndrew Thompson 		uaudio_mixer_determine_class(&iot[id], &mix);
26023a3f90c6SAndrew Thompson 		mix.type = MIX_ON_OFF;
26033a3f90c6SAndrew Thompson 
26043a3f90c6SAndrew Thompson 		uaudio_mixer_add_ctl(sc, &mix);
26053a3f90c6SAndrew Thompson 	}
26063a3f90c6SAndrew Thompson }
26073a3f90c6SAndrew Thompson 
26083a3f90c6SAndrew Thompson static const void *
26093a3f90c6SAndrew Thompson uaudio_mixer_verify_desc(const void *arg, uint32_t len)
26103a3f90c6SAndrew Thompson {
26114c21be9bSRebecca Cran 	const struct usb_audio_mixer_unit_1 *d1;
26124c21be9bSRebecca Cran 	const struct usb_audio_extension_unit_1 *e1;
26134c21be9bSRebecca Cran 	const struct usb_audio_processing_unit_1 *u1;
26143a3f90c6SAndrew Thompson 
26153a3f90c6SAndrew Thompson 	union {
2616760bc48eSAndrew Thompson 		const struct usb_descriptor *desc;
26174c21be9bSRebecca Cran 		const struct usb_audio_input_terminal *it;
26184c21be9bSRebecca Cran 		const struct usb_audio_output_terminal *ot;
26194c21be9bSRebecca Cran 		const struct usb_audio_mixer_unit_0 *mu;
26204c21be9bSRebecca Cran 		const struct usb_audio_selector_unit *su;
26214c21be9bSRebecca Cran 		const struct usb_audio_feature_unit *fu;
26224c21be9bSRebecca Cran 		const struct usb_audio_processing_unit_0 *pu;
26234c21be9bSRebecca Cran 		const struct usb_audio_extension_unit_0 *eu;
26243a3f90c6SAndrew Thompson 	}     u;
26253a3f90c6SAndrew Thompson 
26263a3f90c6SAndrew Thompson 	u.desc = arg;
26273a3f90c6SAndrew Thompson 
26283a3f90c6SAndrew Thompson 	if (u.desc == NULL) {
26293a3f90c6SAndrew Thompson 		goto error;
26303a3f90c6SAndrew Thompson 	}
26313a3f90c6SAndrew Thompson 	if (u.desc->bDescriptorType != UDESC_CS_INTERFACE) {
26323a3f90c6SAndrew Thompson 		goto error;
26333a3f90c6SAndrew Thompson 	}
26343a3f90c6SAndrew Thompson 	switch (u.desc->bDescriptorSubtype) {
26353a3f90c6SAndrew Thompson 	case UDESCSUB_AC_INPUT:
26363a3f90c6SAndrew Thompson 		len += sizeof(*u.it);
26373a3f90c6SAndrew Thompson 		break;
26383a3f90c6SAndrew Thompson 
26393a3f90c6SAndrew Thompson 	case UDESCSUB_AC_OUTPUT:
26403a3f90c6SAndrew Thompson 		len += sizeof(*u.ot);
26413a3f90c6SAndrew Thompson 		break;
26423a3f90c6SAndrew Thompson 
26433a3f90c6SAndrew Thompson 	case UDESCSUB_AC_MIXER:
26443a3f90c6SAndrew Thompson 		len += sizeof(*u.mu);
26453a3f90c6SAndrew Thompson 
26463a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
26473a3f90c6SAndrew Thompson 			goto error;
26483a3f90c6SAndrew Thompson 		}
26493a3f90c6SAndrew Thompson 		len += u.mu->bNrInPins;
26503a3f90c6SAndrew Thompson 
26513a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
26523a3f90c6SAndrew Thompson 			goto error;
26533a3f90c6SAndrew Thompson 		}
26543a3f90c6SAndrew Thompson 		d1 = (const void *)(u.mu->baSourceId + u.mu->bNrInPins);
26553a3f90c6SAndrew Thompson 
26563a3f90c6SAndrew Thompson 		len += sizeof(*d1);
26573a3f90c6SAndrew Thompson 		break;
26583a3f90c6SAndrew Thompson 
26593a3f90c6SAndrew Thompson 	case UDESCSUB_AC_SELECTOR:
26603a3f90c6SAndrew Thompson 		len += sizeof(*u.su);
26613a3f90c6SAndrew Thompson 
26623a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
26633a3f90c6SAndrew Thompson 			goto error;
26643a3f90c6SAndrew Thompson 		}
26653a3f90c6SAndrew Thompson 		len += u.su->bNrInPins;
26663a3f90c6SAndrew Thompson 		break;
26673a3f90c6SAndrew Thompson 
26683a3f90c6SAndrew Thompson 	case UDESCSUB_AC_FEATURE:
26693a3f90c6SAndrew Thompson 		len += (sizeof(*u.fu) + 1);
26703a3f90c6SAndrew Thompson 		break;
26713a3f90c6SAndrew Thompson 
26723a3f90c6SAndrew Thompson 	case UDESCSUB_AC_PROCESSING:
26733a3f90c6SAndrew Thompson 		len += sizeof(*u.pu);
26743a3f90c6SAndrew Thompson 
26753a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
26763a3f90c6SAndrew Thompson 			goto error;
26773a3f90c6SAndrew Thompson 		}
26783a3f90c6SAndrew Thompson 		len += u.pu->bNrInPins;
26793a3f90c6SAndrew Thompson 
26803a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
26813a3f90c6SAndrew Thompson 			goto error;
26823a3f90c6SAndrew Thompson 		}
26833a3f90c6SAndrew Thompson 		u1 = (const void *)(u.pu->baSourceId + u.pu->bNrInPins);
26843a3f90c6SAndrew Thompson 
26853a3f90c6SAndrew Thompson 		len += sizeof(*u1);
26863a3f90c6SAndrew Thompson 
26873a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
26883a3f90c6SAndrew Thompson 			goto error;
26893a3f90c6SAndrew Thompson 		}
26903a3f90c6SAndrew Thompson 		len += u1->bControlSize;
26913a3f90c6SAndrew Thompson 
26923a3f90c6SAndrew Thompson 		break;
26933a3f90c6SAndrew Thompson 
26943a3f90c6SAndrew Thompson 	case UDESCSUB_AC_EXTENSION:
26953a3f90c6SAndrew Thompson 		len += sizeof(*u.eu);
26963a3f90c6SAndrew Thompson 
26973a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
26983a3f90c6SAndrew Thompson 			goto error;
26993a3f90c6SAndrew Thompson 		}
27003a3f90c6SAndrew Thompson 		len += u.eu->bNrInPins;
27013a3f90c6SAndrew Thompson 
27023a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
27033a3f90c6SAndrew Thompson 			goto error;
27043a3f90c6SAndrew Thompson 		}
27053a3f90c6SAndrew Thompson 		e1 = (const void *)(u.eu->baSourceId + u.eu->bNrInPins);
27063a3f90c6SAndrew Thompson 
27073a3f90c6SAndrew Thompson 		len += sizeof(*e1);
27083a3f90c6SAndrew Thompson 
27093a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
27103a3f90c6SAndrew Thompson 			goto error;
27113a3f90c6SAndrew Thompson 		}
27123a3f90c6SAndrew Thompson 		len += e1->bControlSize;
27133a3f90c6SAndrew Thompson 		break;
27143a3f90c6SAndrew Thompson 
27153a3f90c6SAndrew Thompson 	default:
27163a3f90c6SAndrew Thompson 		goto error;
27173a3f90c6SAndrew Thompson 	}
27183a3f90c6SAndrew Thompson 
27193a3f90c6SAndrew Thompson 	if (u.desc->bLength < len) {
27203a3f90c6SAndrew Thompson 		goto error;
27213a3f90c6SAndrew Thompson 	}
27223a3f90c6SAndrew Thompson 	return (u.desc);
27233a3f90c6SAndrew Thompson 
27243a3f90c6SAndrew Thompson error:
27253a3f90c6SAndrew Thompson 	if (u.desc) {
27263a3f90c6SAndrew Thompson 		DPRINTF("invalid descriptor, type=%d, "
27273a3f90c6SAndrew Thompson 		    "sub_type=%d, len=%d of %d bytes\n",
27283a3f90c6SAndrew Thompson 		    u.desc->bDescriptorType,
27293a3f90c6SAndrew Thompson 		    u.desc->bDescriptorSubtype,
27303a3f90c6SAndrew Thompson 		    u.desc->bLength, len);
27313a3f90c6SAndrew Thompson 	}
27323a3f90c6SAndrew Thompson 	return (NULL);
27333a3f90c6SAndrew Thompson }
27343a3f90c6SAndrew Thompson 
2735e2524b2eSHans Petter Selasky static const void *
2736e2524b2eSHans Petter Selasky uaudio20_mixer_verify_desc(const void *arg, uint32_t len)
27373a3f90c6SAndrew Thompson {
2738e2524b2eSHans Petter Selasky 	const struct usb_audio20_mixer_unit_1 *d1;
2739e2524b2eSHans Petter Selasky 	const struct usb_audio20_extension_unit_1 *e1;
2740e2524b2eSHans Petter Selasky 	const struct usb_audio20_processing_unit_1 *u1;
2741e2524b2eSHans Petter Selasky 	const struct usb_audio20_clock_selector_unit_1 *c1;
27423a3f90c6SAndrew Thompson 
2743e2524b2eSHans Petter Selasky 	union {
2744e2524b2eSHans Petter Selasky 		const struct usb_descriptor *desc;
2745e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_source_unit *csrc;
2746e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_selector_unit_0 *csel;
2747e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_multiplier_unit *cmul;
2748e2524b2eSHans Petter Selasky 		const struct usb_audio20_input_terminal *it;
2749e2524b2eSHans Petter Selasky 		const struct usb_audio20_output_terminal *ot;
2750e2524b2eSHans Petter Selasky 		const struct usb_audio20_mixer_unit_0 *mu;
2751e2524b2eSHans Petter Selasky 		const struct usb_audio20_selector_unit *su;
2752e2524b2eSHans Petter Selasky 		const struct usb_audio20_feature_unit *fu;
2753e2524b2eSHans Petter Selasky 		const struct usb_audio20_sample_rate_unit *ru;
2754e2524b2eSHans Petter Selasky 		const struct usb_audio20_processing_unit_0 *pu;
2755e2524b2eSHans Petter Selasky 		const struct usb_audio20_extension_unit_0 *eu;
2756e2524b2eSHans Petter Selasky 		const struct usb_audio20_effect_unit *ef;
2757e2524b2eSHans Petter Selasky 	}     u;
27583a3f90c6SAndrew Thompson 
2759e2524b2eSHans Petter Selasky 	u.desc = arg;
27603a3f90c6SAndrew Thompson 
2761e2524b2eSHans Petter Selasky 	if (u.desc == NULL)
2762e2524b2eSHans Petter Selasky 		goto error;
2763e2524b2eSHans Petter Selasky 
2764e2524b2eSHans Petter Selasky 	if (u.desc->bDescriptorType != UDESC_CS_INTERFACE)
2765e2524b2eSHans Petter Selasky 		goto error;
2766e2524b2eSHans Petter Selasky 
2767e2524b2eSHans Petter Selasky 	switch (u.desc->bDescriptorSubtype) {
2768e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_INPUT:
2769e2524b2eSHans Petter Selasky 		len += sizeof(*u.it);
2770e2524b2eSHans Petter Selasky 		break;
2771e2524b2eSHans Petter Selasky 
2772e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_OUTPUT:
2773e2524b2eSHans Petter Selasky 		len += sizeof(*u.ot);
2774e2524b2eSHans Petter Selasky 		break;
2775e2524b2eSHans Petter Selasky 
2776e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_MIXER:
2777e2524b2eSHans Petter Selasky 		len += sizeof(*u.mu);
2778e2524b2eSHans Petter Selasky 
2779e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
2780e2524b2eSHans Petter Selasky 			goto error;
2781e2524b2eSHans Petter Selasky 		len += u.mu->bNrInPins;
2782e2524b2eSHans Petter Selasky 
2783e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
2784e2524b2eSHans Petter Selasky 			goto error;
2785e2524b2eSHans Petter Selasky 
2786e2524b2eSHans Petter Selasky 		d1 = (const void *)(u.mu->baSourceId + u.mu->bNrInPins);
2787e2524b2eSHans Petter Selasky 
2788e2524b2eSHans Petter Selasky 		len += sizeof(*d1) + d1->bNrChannels;
2789e2524b2eSHans Petter Selasky 		break;
2790e2524b2eSHans Petter Selasky 
2791e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_SELECTOR:
2792e2524b2eSHans Petter Selasky 		len += sizeof(*u.su);
2793e2524b2eSHans Petter Selasky 
2794e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
2795e2524b2eSHans Petter Selasky 			goto error;
2796e2524b2eSHans Petter Selasky 
2797e2524b2eSHans Petter Selasky 		len += u.su->bNrInPins;
2798e2524b2eSHans Petter Selasky 		break;
2799e2524b2eSHans Petter Selasky 
2800e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_FEATURE:
2801e2524b2eSHans Petter Selasky 		len += sizeof(*u.fu) + 1;
2802e2524b2eSHans Petter Selasky 
2803e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
2804e2524b2eSHans Petter Selasky 			goto error;
2805e2524b2eSHans Petter Selasky 		break;
2806e2524b2eSHans Petter Selasky 
2807e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_EFFECT:
2808e2524b2eSHans Petter Selasky 		len += sizeof(*u.ef) + 4;
2809e2524b2eSHans Petter Selasky 		break;
2810e2524b2eSHans Petter Selasky 
2811e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_PROCESSING_V2:
2812e2524b2eSHans Petter Selasky 		len += sizeof(*u.pu);
2813e2524b2eSHans Petter Selasky 
2814e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
2815e2524b2eSHans Petter Selasky 			goto error;
2816e2524b2eSHans Petter Selasky 
2817e2524b2eSHans Petter Selasky 		len += u.pu->bNrInPins;
2818e2524b2eSHans Petter Selasky 
2819e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
2820e2524b2eSHans Petter Selasky 			goto error;
2821e2524b2eSHans Petter Selasky 
2822e2524b2eSHans Petter Selasky 		u1 = (const void *)(u.pu->baSourceId + u.pu->bNrInPins);
2823e2524b2eSHans Petter Selasky 
2824e2524b2eSHans Petter Selasky 		len += sizeof(*u1);
2825e2524b2eSHans Petter Selasky 		break;
2826e2524b2eSHans Petter Selasky 
2827e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_EXTENSION_V2:
2828e2524b2eSHans Petter Selasky 		len += sizeof(*u.eu);
2829e2524b2eSHans Petter Selasky 
2830e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
2831e2524b2eSHans Petter Selasky 			goto error;
2832e2524b2eSHans Petter Selasky 
2833e2524b2eSHans Petter Selasky 		len += u.eu->bNrInPins;
2834e2524b2eSHans Petter Selasky 
2835e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
2836e2524b2eSHans Petter Selasky 			goto error;
2837e2524b2eSHans Petter Selasky 
2838e2524b2eSHans Petter Selasky 		e1 = (const void *)(u.eu->baSourceId + u.eu->bNrInPins);
2839e2524b2eSHans Petter Selasky 
2840e2524b2eSHans Petter Selasky 		len += sizeof(*e1);
2841e2524b2eSHans Petter Selasky 		break;
2842e2524b2eSHans Petter Selasky 
2843e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_CLOCK_SRC:
2844e2524b2eSHans Petter Selasky 		len += sizeof(*u.csrc);
2845e2524b2eSHans Petter Selasky 		break;
2846e2524b2eSHans Petter Selasky 
2847e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_CLOCK_SEL:
2848e2524b2eSHans Petter Selasky 		len += sizeof(*u.csel);
2849e2524b2eSHans Petter Selasky 
2850e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
2851e2524b2eSHans Petter Selasky 			goto error;
2852e2524b2eSHans Petter Selasky 
2853e2524b2eSHans Petter Selasky 		len += u.csel->bNrInPins;
2854e2524b2eSHans Petter Selasky 
2855e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
2856e2524b2eSHans Petter Selasky 			goto error;
2857e2524b2eSHans Petter Selasky 
2858e2524b2eSHans Petter Selasky 		c1 = (const void *)(u.csel->baCSourceId + u.csel->bNrInPins);
2859e2524b2eSHans Petter Selasky 
2860e2524b2eSHans Petter Selasky 		len += sizeof(*c1);
2861e2524b2eSHans Petter Selasky 		break;
2862e2524b2eSHans Petter Selasky 
2863e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_CLOCK_MUL:
2864e2524b2eSHans Petter Selasky 		len += sizeof(*u.cmul);
2865e2524b2eSHans Petter Selasky 		break;
2866e2524b2eSHans Petter Selasky 
2867e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_SAMPLE_RT:
2868e2524b2eSHans Petter Selasky 		len += sizeof(*u.ru);
2869e2524b2eSHans Petter Selasky 		break;
2870e2524b2eSHans Petter Selasky 
2871e2524b2eSHans Petter Selasky 	default:
2872e2524b2eSHans Petter Selasky 		goto error;
28733a3f90c6SAndrew Thompson 	}
28743a3f90c6SAndrew Thompson 
2875e2524b2eSHans Petter Selasky 	if (u.desc->bLength < len)
2876e2524b2eSHans Petter Selasky 		goto error;
2877e2524b2eSHans Petter Selasky 
2878e2524b2eSHans Petter Selasky 	return (u.desc);
2879e2524b2eSHans Petter Selasky 
2880e2524b2eSHans Petter Selasky error:
2881e2524b2eSHans Petter Selasky 	if (u.desc) {
2882e2524b2eSHans Petter Selasky 		DPRINTF("invalid descriptor, type=%d, "
2883e2524b2eSHans Petter Selasky 		    "sub_type=%d, len=%d of %d bytes\n",
2884e2524b2eSHans Petter Selasky 		    u.desc->bDescriptorType,
2885e2524b2eSHans Petter Selasky 		    u.desc->bDescriptorSubtype,
2886e2524b2eSHans Petter Selasky 		    u.desc->bLength, len);
2887e2524b2eSHans Petter Selasky 	}
2888e2524b2eSHans Petter Selasky 	return (NULL);
2889e2524b2eSHans Petter Selasky }
28903a3f90c6SAndrew Thompson 
28914c21be9bSRebecca Cran static struct usb_audio_cluster
28923a3f90c6SAndrew Thompson uaudio_mixer_get_cluster(uint8_t id, const struct uaudio_terminal_node *iot)
28933a3f90c6SAndrew Thompson {
28944c21be9bSRebecca Cran 	struct usb_audio_cluster r;
2895760bc48eSAndrew Thompson 	const struct usb_descriptor *dp;
28963a3f90c6SAndrew Thompson 	uint8_t i;
28973a3f90c6SAndrew Thompson 
28983a3f90c6SAndrew Thompson 	for (i = 0; i < UAUDIO_RECURSE_LIMIT; i++) {	/* avoid infinite loops */
28993a3f90c6SAndrew Thompson 		dp = iot[id].u.desc;
29003a3f90c6SAndrew Thompson 		if (dp == NULL) {
29013a3f90c6SAndrew Thompson 			goto error;
29023a3f90c6SAndrew Thompson 		}
29033a3f90c6SAndrew Thompson 		switch (dp->bDescriptorSubtype) {
29043a3f90c6SAndrew Thompson 		case UDESCSUB_AC_INPUT:
2905e2524b2eSHans Petter Selasky 			r.bNrChannels = iot[id].u.it_v1->bNrChannels;
2906e2524b2eSHans Petter Selasky 			r.wChannelConfig[0] = iot[id].u.it_v1->wChannelConfig[0];
2907e2524b2eSHans Petter Selasky 			r.wChannelConfig[1] = iot[id].u.it_v1->wChannelConfig[1];
2908e2524b2eSHans Petter Selasky 			r.iChannelNames = iot[id].u.it_v1->iChannelNames;
29093a3f90c6SAndrew Thompson 			goto done;
29103a3f90c6SAndrew Thompson 
29113a3f90c6SAndrew Thompson 		case UDESCSUB_AC_OUTPUT:
2912e2524b2eSHans Petter Selasky 			id = iot[id].u.ot_v1->bSourceId;
29133a3f90c6SAndrew Thompson 			break;
29143a3f90c6SAndrew Thompson 
29153a3f90c6SAndrew Thompson 		case UDESCSUB_AC_MIXER:
29164c21be9bSRebecca Cran 			r = *(const struct usb_audio_cluster *)
2917e2524b2eSHans Petter Selasky 			    &iot[id].u.mu_v1->baSourceId[
2918e2524b2eSHans Petter Selasky 			    iot[id].u.mu_v1->bNrInPins];
29193a3f90c6SAndrew Thompson 			goto done;
29203a3f90c6SAndrew Thompson 
29213a3f90c6SAndrew Thompson 		case UDESCSUB_AC_SELECTOR:
2922e2524b2eSHans Petter Selasky 			if (iot[id].u.su_v1->bNrInPins > 0) {
29233a3f90c6SAndrew Thompson 				/* XXX This is not really right */
2924e2524b2eSHans Petter Selasky 				id = iot[id].u.su_v1->baSourceId[0];
29253a3f90c6SAndrew Thompson 			}
29263a3f90c6SAndrew Thompson 			break;
29273a3f90c6SAndrew Thompson 
29283a3f90c6SAndrew Thompson 		case UDESCSUB_AC_FEATURE:
2929e2524b2eSHans Petter Selasky 			id = iot[id].u.fu_v1->bSourceId;
29303a3f90c6SAndrew Thompson 			break;
29313a3f90c6SAndrew Thompson 
29323a3f90c6SAndrew Thompson 		case UDESCSUB_AC_PROCESSING:
29334c21be9bSRebecca Cran 			r = *((const struct usb_audio_cluster *)
2934e2524b2eSHans Petter Selasky 			    &iot[id].u.pu_v1->baSourceId[
2935e2524b2eSHans Petter Selasky 			    iot[id].u.pu_v1->bNrInPins]);
29363a3f90c6SAndrew Thompson 			goto done;
29373a3f90c6SAndrew Thompson 
29383a3f90c6SAndrew Thompson 		case UDESCSUB_AC_EXTENSION:
29394c21be9bSRebecca Cran 			r = *((const struct usb_audio_cluster *)
2940e2524b2eSHans Petter Selasky 			    &iot[id].u.eu_v1->baSourceId[
2941e2524b2eSHans Petter Selasky 			    iot[id].u.eu_v1->bNrInPins]);
29423a3f90c6SAndrew Thompson 			goto done;
29433a3f90c6SAndrew Thompson 
29443a3f90c6SAndrew Thompson 		default:
29453a3f90c6SAndrew Thompson 			goto error;
29463a3f90c6SAndrew Thompson 		}
29473a3f90c6SAndrew Thompson 	}
29483a3f90c6SAndrew Thompson error:
29493a3f90c6SAndrew Thompson 	DPRINTF("bad data\n");
29506f068a43SHans Petter Selasky 	memset(&r, 0, sizeof(r));
29513a3f90c6SAndrew Thompson done:
29523a3f90c6SAndrew Thompson 	return (r);
29533a3f90c6SAndrew Thompson }
29543a3f90c6SAndrew Thompson 
2955e2524b2eSHans Petter Selasky static struct usb_audio20_cluster
2956e2524b2eSHans Petter Selasky uaudio20_mixer_get_cluster(uint8_t id, const struct uaudio_terminal_node *iot)
29573a3f90c6SAndrew Thompson {
2958e2524b2eSHans Petter Selasky 	struct usb_audio20_cluster r;
2959e2524b2eSHans Petter Selasky 	const struct usb_descriptor *dp;
2960e2524b2eSHans Petter Selasky 	uint8_t i;
29613a3f90c6SAndrew Thompson 
2962e2524b2eSHans Petter Selasky 	for (i = 0; i < UAUDIO_RECURSE_LIMIT; i++) {	/* avoid infinite loops */
2963e2524b2eSHans Petter Selasky 		dp = iot[id].u.desc;
2964e2524b2eSHans Petter Selasky 		if (dp == NULL)
2965e2524b2eSHans Petter Selasky 			goto error;
2966e2524b2eSHans Petter Selasky 
2967e2524b2eSHans Petter Selasky 		switch (dp->bDescriptorSubtype) {
2968e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_INPUT:
2969e2524b2eSHans Petter Selasky 			r.bNrChannels = iot[id].u.it_v2->bNrChannels;
2970e2524b2eSHans Petter Selasky 			r.bmChannelConfig[0] = iot[id].u.it_v2->bmChannelConfig[0];
2971e2524b2eSHans Petter Selasky 			r.bmChannelConfig[1] = iot[id].u.it_v2->bmChannelConfig[1];
2972e2524b2eSHans Petter Selasky 			r.bmChannelConfig[2] = iot[id].u.it_v2->bmChannelConfig[2];
2973e2524b2eSHans Petter Selasky 			r.bmChannelConfig[3] = iot[id].u.it_v2->bmChannelConfig[3];
2974e2524b2eSHans Petter Selasky 			r.iChannelNames = iot[id].u.it_v2->iTerminal;
2975e2524b2eSHans Petter Selasky 			goto done;
2976e2524b2eSHans Petter Selasky 
2977e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_OUTPUT:
2978e2524b2eSHans Petter Selasky 			id = iot[id].u.ot_v2->bSourceId;
29793a3f90c6SAndrew Thompson 			break;
29803a3f90c6SAndrew Thompson 
2981e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_MIXER:
2982e2524b2eSHans Petter Selasky 			r = *(const struct usb_audio20_cluster *)
2983e2524b2eSHans Petter Selasky 			    &iot[id].u.mu_v2->baSourceId[
2984e2524b2eSHans Petter Selasky 			    iot[id].u.mu_v2->bNrInPins];
2985e2524b2eSHans Petter Selasky 			goto done;
2986e2524b2eSHans Petter Selasky 
2987e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_SELECTOR:
2988e2524b2eSHans Petter Selasky 			if (iot[id].u.su_v2->bNrInPins > 0) {
2989e2524b2eSHans Petter Selasky 				/* XXX This is not really right */
2990e2524b2eSHans Petter Selasky 				id = iot[id].u.su_v2->baSourceId[0];
2991e2524b2eSHans Petter Selasky 			}
2992e2524b2eSHans Petter Selasky 			break;
2993e2524b2eSHans Petter Selasky 
2994e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_SAMPLE_RT:
2995e2524b2eSHans Petter Selasky 			id = iot[id].u.ru_v2->bSourceId;
2996e2524b2eSHans Petter Selasky 			break;
2997e2524b2eSHans Petter Selasky 
2998e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_EFFECT:
2999e2524b2eSHans Petter Selasky 			id = iot[id].u.ef_v2->bSourceId;
3000e2524b2eSHans Petter Selasky 			break;
3001e2524b2eSHans Petter Selasky 
3002e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_FEATURE:
3003e2524b2eSHans Petter Selasky 			id = iot[id].u.fu_v2->bSourceId;
3004e2524b2eSHans Petter Selasky 			break;
3005e2524b2eSHans Petter Selasky 
3006e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_PROCESSING_V2:
3007e2524b2eSHans Petter Selasky 			r = *((const struct usb_audio20_cluster *)
3008e2524b2eSHans Petter Selasky 			    &iot[id].u.pu_v2->baSourceId[
3009e2524b2eSHans Petter Selasky 			    iot[id].u.pu_v2->bNrInPins]);
3010e2524b2eSHans Petter Selasky 			goto done;
3011e2524b2eSHans Petter Selasky 
3012e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_EXTENSION_V2:
3013e2524b2eSHans Petter Selasky 			r = *((const struct usb_audio20_cluster *)
3014e2524b2eSHans Petter Selasky 			    &iot[id].u.eu_v2->baSourceId[
3015e2524b2eSHans Petter Selasky 			    iot[id].u.eu_v2->bNrInPins]);
3016e2524b2eSHans Petter Selasky 			goto done;
3017e2524b2eSHans Petter Selasky 
3018e2524b2eSHans Petter Selasky 		default:
3019e2524b2eSHans Petter Selasky 			goto error;
3020e2524b2eSHans Petter Selasky 		}
3021e2524b2eSHans Petter Selasky 	}
3022e2524b2eSHans Petter Selasky error:
3023e2524b2eSHans Petter Selasky 	DPRINTF("Bad data!\n");
3024e2524b2eSHans Petter Selasky 	memset(&r, 0, sizeof(r));
3025e2524b2eSHans Petter Selasky done:
3026e2524b2eSHans Petter Selasky 	return (r);
3027e2524b2eSHans Petter Selasky }
30283a3f90c6SAndrew Thompson 
30293a3f90c6SAndrew Thompson static uint16_t
30303a3f90c6SAndrew Thompson uaudio_mixer_determine_class(const struct uaudio_terminal_node *iot,
30313a3f90c6SAndrew Thompson     struct uaudio_mixer_node *mix)
30323a3f90c6SAndrew Thompson {
30333a3f90c6SAndrew Thompson 	uint16_t terminal_type = 0x0000;
30343a3f90c6SAndrew Thompson 	const struct uaudio_terminal_node *input[2];
30353a3f90c6SAndrew Thompson 	const struct uaudio_terminal_node *output[2];
30363a3f90c6SAndrew Thompson 
30373a3f90c6SAndrew Thompson 	input[0] = uaudio_mixer_get_input(iot, 0);
30383a3f90c6SAndrew Thompson 	input[1] = uaudio_mixer_get_input(iot, 1);
30393a3f90c6SAndrew Thompson 
30403a3f90c6SAndrew Thompson 	output[0] = uaudio_mixer_get_output(iot, 0);
30413a3f90c6SAndrew Thompson 	output[1] = uaudio_mixer_get_output(iot, 1);
30423a3f90c6SAndrew Thompson 
30433a3f90c6SAndrew Thompson 	/*
30443a3f90c6SAndrew Thompson 	 * check if there is only
30453a3f90c6SAndrew Thompson 	 * one output terminal:
30463a3f90c6SAndrew Thompson 	 */
30473a3f90c6SAndrew Thompson 	if (output[0] && (!output[1])) {
3048e2524b2eSHans Petter Selasky 		terminal_type =
3049e2524b2eSHans Petter Selasky 		    UGETW(output[0]->u.ot_v1->wTerminalType);
30503a3f90c6SAndrew Thompson 	}
30513a3f90c6SAndrew Thompson 	/*
30523a3f90c6SAndrew Thompson 	 * If the only output terminal is USB,
30533a3f90c6SAndrew Thompson 	 * the class is UAC_RECORD.
30543a3f90c6SAndrew Thompson 	 */
30553a3f90c6SAndrew Thompson 	if ((terminal_type & 0xff00) == (UAT_UNDEFINED & 0xff00)) {
30563a3f90c6SAndrew Thompson 
30573a3f90c6SAndrew Thompson 		mix->class = UAC_RECORD;
30583a3f90c6SAndrew Thompson 		if (input[0] && (!input[1])) {
3059e2524b2eSHans Petter Selasky 			terminal_type =
3060e2524b2eSHans Petter Selasky 			    UGETW(input[0]->u.it_v1->wTerminalType);
30613a3f90c6SAndrew Thompson 		} else {
30623a3f90c6SAndrew Thompson 			terminal_type = 0;
30633a3f90c6SAndrew Thompson 		}
30643a3f90c6SAndrew Thompson 		goto done;
30653a3f90c6SAndrew Thompson 	}
30663a3f90c6SAndrew Thompson 	/*
30673a3f90c6SAndrew Thompson 	 * if the unit is connected to just
30683a3f90c6SAndrew Thompson 	 * one input terminal, the
30693a3f90c6SAndrew Thompson 	 * class is UAC_INPUT:
30703a3f90c6SAndrew Thompson 	 */
30713a3f90c6SAndrew Thompson 	if (input[0] && (!input[1])) {
30723a3f90c6SAndrew Thompson 		mix->class = UAC_INPUT;
3073e2524b2eSHans Petter Selasky 		terminal_type =
3074e2524b2eSHans Petter Selasky 		    UGETW(input[0]->u.it_v1->wTerminalType);
3075e2524b2eSHans Petter Selasky 		goto done;
3076e2524b2eSHans Petter Selasky 	}
3077e2524b2eSHans Petter Selasky 	/*
3078e2524b2eSHans Petter Selasky 	 * Otherwise, the class is UAC_OUTPUT.
3079e2524b2eSHans Petter Selasky 	 */
3080e2524b2eSHans Petter Selasky 	mix->class = UAC_OUTPUT;
3081e2524b2eSHans Petter Selasky done:
3082e2524b2eSHans Petter Selasky 	return (terminal_type);
3083e2524b2eSHans Petter Selasky }
3084e2524b2eSHans Petter Selasky 
3085e2524b2eSHans Petter Selasky static uint16_t
3086e2524b2eSHans Petter Selasky uaudio20_mixer_determine_class(const struct uaudio_terminal_node *iot,
3087e2524b2eSHans Petter Selasky     struct uaudio_mixer_node *mix)
3088e2524b2eSHans Petter Selasky {
3089e2524b2eSHans Petter Selasky 	uint16_t terminal_type = 0x0000;
3090e2524b2eSHans Petter Selasky 	const struct uaudio_terminal_node *input[2];
3091e2524b2eSHans Petter Selasky 	const struct uaudio_terminal_node *output[2];
3092e2524b2eSHans Petter Selasky 
3093e2524b2eSHans Petter Selasky 	input[0] = uaudio_mixer_get_input(iot, 0);
3094e2524b2eSHans Petter Selasky 	input[1] = uaudio_mixer_get_input(iot, 1);
3095e2524b2eSHans Petter Selasky 
3096e2524b2eSHans Petter Selasky 	output[0] = uaudio_mixer_get_output(iot, 0);
3097e2524b2eSHans Petter Selasky 	output[1] = uaudio_mixer_get_output(iot, 1);
3098e2524b2eSHans Petter Selasky 
3099e2524b2eSHans Petter Selasky 	/*
3100e2524b2eSHans Petter Selasky 	 * check if there is only
3101e2524b2eSHans Petter Selasky 	 * one output terminal:
3102e2524b2eSHans Petter Selasky 	 */
3103e2524b2eSHans Petter Selasky 	if (output[0] && (!output[1]))
3104e2524b2eSHans Petter Selasky 		terminal_type = UGETW(output[0]->u.ot_v2->wTerminalType);
3105e2524b2eSHans Petter Selasky 	/*
3106e2524b2eSHans Petter Selasky 	 * If the only output terminal is USB,
3107e2524b2eSHans Petter Selasky 	 * the class is UAC_RECORD.
3108e2524b2eSHans Petter Selasky 	 */
3109e2524b2eSHans Petter Selasky 	if ((terminal_type & 0xff00) == (UAT_UNDEFINED & 0xff00)) {
3110e2524b2eSHans Petter Selasky 
3111e2524b2eSHans Petter Selasky 		mix->class = UAC_RECORD;
3112e2524b2eSHans Petter Selasky 		if (input[0] && (!input[1])) {
3113e2524b2eSHans Petter Selasky 			terminal_type =
3114e2524b2eSHans Petter Selasky 			    UGETW(input[0]->u.it_v2->wTerminalType);
3115e2524b2eSHans Petter Selasky 		} else {
3116e2524b2eSHans Petter Selasky 			terminal_type = 0;
3117e2524b2eSHans Petter Selasky 		}
3118e2524b2eSHans Petter Selasky 		goto done;
3119e2524b2eSHans Petter Selasky 	}
3120e2524b2eSHans Petter Selasky 	/*
3121e2524b2eSHans Petter Selasky 	 * if the unit is connected to just
3122e2524b2eSHans Petter Selasky 	 * one input terminal, the
3123e2524b2eSHans Petter Selasky 	 * class is UAC_INPUT:
3124e2524b2eSHans Petter Selasky 	 */
3125e2524b2eSHans Petter Selasky 	if (input[0] && (!input[1])) {
3126e2524b2eSHans Petter Selasky 		mix->class = UAC_INPUT;
3127e2524b2eSHans Petter Selasky 		terminal_type =
3128e2524b2eSHans Petter Selasky 		    UGETW(input[0]->u.it_v2->wTerminalType);
31293a3f90c6SAndrew Thompson 		goto done;
31303a3f90c6SAndrew Thompson 	}
31313a3f90c6SAndrew Thompson 	/*
31323a3f90c6SAndrew Thompson 	 * Otherwise, the class is UAC_OUTPUT.
31333a3f90c6SAndrew Thompson 	 */
31343a3f90c6SAndrew Thompson 	mix->class = UAC_OUTPUT;
31353a3f90c6SAndrew Thompson done:
31363a3f90c6SAndrew Thompson 	return (terminal_type);
31373a3f90c6SAndrew Thompson }
31383a3f90c6SAndrew Thompson 
31393a3f90c6SAndrew Thompson struct uaudio_tt_to_feature {
31403a3f90c6SAndrew Thompson 	uint16_t terminal_type;
31413a3f90c6SAndrew Thompson 	uint16_t feature;
31423a3f90c6SAndrew Thompson };
31433a3f90c6SAndrew Thompson 
31443a3f90c6SAndrew Thompson static const struct uaudio_tt_to_feature uaudio_tt_to_feature[] = {
31453a3f90c6SAndrew Thompson 
31463a3f90c6SAndrew Thompson 	{UAT_STREAM, SOUND_MIXER_PCM},
31473a3f90c6SAndrew Thompson 
31483a3f90c6SAndrew Thompson 	{UATI_MICROPHONE, SOUND_MIXER_MIC},
31493a3f90c6SAndrew Thompson 	{UATI_DESKMICROPHONE, SOUND_MIXER_MIC},
31503a3f90c6SAndrew Thompson 	{UATI_PERSONALMICROPHONE, SOUND_MIXER_MIC},
31513a3f90c6SAndrew Thompson 	{UATI_OMNIMICROPHONE, SOUND_MIXER_MIC},
31523a3f90c6SAndrew Thompson 	{UATI_MICROPHONEARRAY, SOUND_MIXER_MIC},
31533a3f90c6SAndrew Thompson 	{UATI_PROCMICROPHONEARR, SOUND_MIXER_MIC},
31543a3f90c6SAndrew Thompson 
31553a3f90c6SAndrew Thompson 	{UATO_SPEAKER, SOUND_MIXER_SPEAKER},
31563a3f90c6SAndrew Thompson 	{UATO_DESKTOPSPEAKER, SOUND_MIXER_SPEAKER},
31573a3f90c6SAndrew Thompson 	{UATO_ROOMSPEAKER, SOUND_MIXER_SPEAKER},
31583a3f90c6SAndrew Thompson 	{UATO_COMMSPEAKER, SOUND_MIXER_SPEAKER},
31593a3f90c6SAndrew Thompson 
31603a3f90c6SAndrew Thompson 	{UATE_ANALOGCONN, SOUND_MIXER_LINE},
31613a3f90c6SAndrew Thompson 	{UATE_LINECONN, SOUND_MIXER_LINE},
31623a3f90c6SAndrew Thompson 	{UATE_LEGACYCONN, SOUND_MIXER_LINE},
31633a3f90c6SAndrew Thompson 
31643a3f90c6SAndrew Thompson 	{UATE_DIGITALAUIFC, SOUND_MIXER_ALTPCM},
31653a3f90c6SAndrew Thompson 	{UATE_SPDIF, SOUND_MIXER_ALTPCM},
31663a3f90c6SAndrew Thompson 	{UATE_1394DA, SOUND_MIXER_ALTPCM},
31673a3f90c6SAndrew Thompson 	{UATE_1394DV, SOUND_MIXER_ALTPCM},
31683a3f90c6SAndrew Thompson 
31693a3f90c6SAndrew Thompson 	{UATF_CDPLAYER, SOUND_MIXER_CD},
31703a3f90c6SAndrew Thompson 
31713a3f90c6SAndrew Thompson 	{UATF_SYNTHESIZER, SOUND_MIXER_SYNTH},
31723a3f90c6SAndrew Thompson 
31733a3f90c6SAndrew Thompson 	{UATF_VIDEODISCAUDIO, SOUND_MIXER_VIDEO},
31743a3f90c6SAndrew Thompson 	{UATF_DVDAUDIO, SOUND_MIXER_VIDEO},
31753a3f90c6SAndrew Thompson 	{UATF_TVTUNERAUDIO, SOUND_MIXER_VIDEO},
31763a3f90c6SAndrew Thompson 
31773a3f90c6SAndrew Thompson 	/* telephony terminal types */
31783a3f90c6SAndrew Thompson 	{UATT_UNDEFINED, SOUND_MIXER_PHONEIN},	/* SOUND_MIXER_PHONEOUT */
31793a3f90c6SAndrew Thompson 	{UATT_PHONELINE, SOUND_MIXER_PHONEIN},	/* SOUND_MIXER_PHONEOUT */
31803a3f90c6SAndrew Thompson 	{UATT_TELEPHONE, SOUND_MIXER_PHONEIN},	/* SOUND_MIXER_PHONEOUT */
31813a3f90c6SAndrew Thompson 	{UATT_DOWNLINEPHONE, SOUND_MIXER_PHONEIN},	/* SOUND_MIXER_PHONEOUT */
31823a3f90c6SAndrew Thompson 
31833a3f90c6SAndrew Thompson 	{UATF_RADIORECV, SOUND_MIXER_RADIO},
31843a3f90c6SAndrew Thompson 	{UATF_RADIOXMIT, SOUND_MIXER_RADIO},
31853a3f90c6SAndrew Thompson 
31863a3f90c6SAndrew Thompson 	{UAT_UNDEFINED, SOUND_MIXER_VOLUME},
31873a3f90c6SAndrew Thompson 	{UAT_VENDOR, SOUND_MIXER_VOLUME},
31883a3f90c6SAndrew Thompson 	{UATI_UNDEFINED, SOUND_MIXER_VOLUME},
31893a3f90c6SAndrew Thompson 
31903a3f90c6SAndrew Thompson 	/* output terminal types */
31913a3f90c6SAndrew Thompson 	{UATO_UNDEFINED, SOUND_MIXER_VOLUME},
31923a3f90c6SAndrew Thompson 	{UATO_DISPLAYAUDIO, SOUND_MIXER_VOLUME},
31933a3f90c6SAndrew Thompson 	{UATO_SUBWOOFER, SOUND_MIXER_VOLUME},
31943a3f90c6SAndrew Thompson 	{UATO_HEADPHONES, SOUND_MIXER_VOLUME},
31953a3f90c6SAndrew Thompson 
31963a3f90c6SAndrew Thompson 	/* bidir terminal types */
31973a3f90c6SAndrew Thompson 	{UATB_UNDEFINED, SOUND_MIXER_VOLUME},
31983a3f90c6SAndrew Thompson 	{UATB_HANDSET, SOUND_MIXER_VOLUME},
31993a3f90c6SAndrew Thompson 	{UATB_HEADSET, SOUND_MIXER_VOLUME},
32003a3f90c6SAndrew Thompson 	{UATB_SPEAKERPHONE, SOUND_MIXER_VOLUME},
32013a3f90c6SAndrew Thompson 	{UATB_SPEAKERPHONEESUP, SOUND_MIXER_VOLUME},
32023a3f90c6SAndrew Thompson 	{UATB_SPEAKERPHONEECANC, SOUND_MIXER_VOLUME},
32033a3f90c6SAndrew Thompson 
32043a3f90c6SAndrew Thompson 	/* external terminal types */
32053a3f90c6SAndrew Thompson 	{UATE_UNDEFINED, SOUND_MIXER_VOLUME},
32063a3f90c6SAndrew Thompson 
32073a3f90c6SAndrew Thompson 	/* embedded function terminal types */
32083a3f90c6SAndrew Thompson 	{UATF_UNDEFINED, SOUND_MIXER_VOLUME},
32093a3f90c6SAndrew Thompson 	{UATF_CALIBNOISE, SOUND_MIXER_VOLUME},
32103a3f90c6SAndrew Thompson 	{UATF_EQUNOISE, SOUND_MIXER_VOLUME},
32113a3f90c6SAndrew Thompson 	{UATF_DAT, SOUND_MIXER_VOLUME},
32123a3f90c6SAndrew Thompson 	{UATF_DCC, SOUND_MIXER_VOLUME},
32133a3f90c6SAndrew Thompson 	{UATF_MINIDISK, SOUND_MIXER_VOLUME},
32143a3f90c6SAndrew Thompson 	{UATF_ANALOGTAPE, SOUND_MIXER_VOLUME},
32153a3f90c6SAndrew Thompson 	{UATF_PHONOGRAPH, SOUND_MIXER_VOLUME},
32163a3f90c6SAndrew Thompson 	{UATF_VCRAUDIO, SOUND_MIXER_VOLUME},
32173a3f90c6SAndrew Thompson 	{UATF_SATELLITE, SOUND_MIXER_VOLUME},
32183a3f90c6SAndrew Thompson 	{UATF_CABLETUNER, SOUND_MIXER_VOLUME},
32193a3f90c6SAndrew Thompson 	{UATF_DSS, SOUND_MIXER_VOLUME},
32203a3f90c6SAndrew Thompson 	{UATF_MULTITRACK, SOUND_MIXER_VOLUME},
32213a3f90c6SAndrew Thompson 	{0xffff, SOUND_MIXER_VOLUME},
32223a3f90c6SAndrew Thompson 
32233a3f90c6SAndrew Thompson 	/* default */
32243a3f90c6SAndrew Thompson 	{0x0000, SOUND_MIXER_VOLUME},
32253a3f90c6SAndrew Thompson };
32263a3f90c6SAndrew Thompson 
32273a3f90c6SAndrew Thompson static uint16_t
32283a3f90c6SAndrew Thompson uaudio_mixer_feature_name(const struct uaudio_terminal_node *iot,
32293a3f90c6SAndrew Thompson     struct uaudio_mixer_node *mix)
32303a3f90c6SAndrew Thompson {
32313a3f90c6SAndrew Thompson 	const struct uaudio_tt_to_feature *uat = uaudio_tt_to_feature;
32323a3f90c6SAndrew Thompson 	uint16_t terminal_type = uaudio_mixer_determine_class(iot, mix);
32333a3f90c6SAndrew Thompson 
32343a3f90c6SAndrew Thompson 	if ((mix->class == UAC_RECORD) && (terminal_type == 0)) {
32353a3f90c6SAndrew Thompson 		return (SOUND_MIXER_IMIX);
32363a3f90c6SAndrew Thompson 	}
32373a3f90c6SAndrew Thompson 	while (uat->terminal_type) {
32383a3f90c6SAndrew Thompson 		if (uat->terminal_type == terminal_type) {
32393a3f90c6SAndrew Thompson 			break;
32403a3f90c6SAndrew Thompson 		}
32413a3f90c6SAndrew Thompson 		uat++;
32423a3f90c6SAndrew Thompson 	}
32433a3f90c6SAndrew Thompson 
3244e2524b2eSHans Petter Selasky 	DPRINTF("terminal_type=0x%04x -> %d\n",
3245e2524b2eSHans Petter Selasky 	    terminal_type, uat->feature);
3246e2524b2eSHans Petter Selasky 
3247e2524b2eSHans Petter Selasky 	return (uat->feature);
3248e2524b2eSHans Petter Selasky }
3249e2524b2eSHans Petter Selasky 
3250e2524b2eSHans Petter Selasky static uint16_t
3251e2524b2eSHans Petter Selasky uaudio20_mixer_feature_name(const struct uaudio_terminal_node *iot,
3252e2524b2eSHans Petter Selasky     struct uaudio_mixer_node *mix)
3253e2524b2eSHans Petter Selasky {
3254e2524b2eSHans Petter Selasky 	const struct uaudio_tt_to_feature *uat;
3255e2524b2eSHans Petter Selasky 	uint16_t terminal_type = uaudio20_mixer_determine_class(iot, mix);
3256e2524b2eSHans Petter Selasky 
3257e2524b2eSHans Petter Selasky 	if ((mix->class == UAC_RECORD) && (terminal_type == 0))
3258e2524b2eSHans Petter Selasky 		return (SOUND_MIXER_IMIX);
3259e2524b2eSHans Petter Selasky 
3260e2524b2eSHans Petter Selasky 	for (uat = uaudio_tt_to_feature; uat->terminal_type != 0; uat++) {
3261e2524b2eSHans Petter Selasky 		if (uat->terminal_type == terminal_type)
3262e2524b2eSHans Petter Selasky 			break;
3263e2524b2eSHans Petter Selasky 	}
3264e2524b2eSHans Petter Selasky 
3265e2524b2eSHans Petter Selasky 	DPRINTF("terminal_type=0x%04x -> %d\n",
32663a3f90c6SAndrew Thompson 	    terminal_type, uat->feature);
32673a3f90c6SAndrew Thompson 
32683a3f90c6SAndrew Thompson 	return (uat->feature);
32693a3f90c6SAndrew Thompson }
32703a3f90c6SAndrew Thompson 
32716d917491SHans Petter Selasky static const struct uaudio_terminal_node *
32726d917491SHans Petter Selasky uaudio_mixer_get_input(const struct uaudio_terminal_node *iot, uint8_t i)
32733a3f90c6SAndrew Thompson {
32743a3f90c6SAndrew Thompson 	struct uaudio_terminal_node *root = iot->root;
32753a3f90c6SAndrew Thompson 	uint8_t n;
32763a3f90c6SAndrew Thompson 
32773a3f90c6SAndrew Thompson 	n = iot->usr.id_max;
32783a3f90c6SAndrew Thompson 	do {
32793a3f90c6SAndrew Thompson 		if (iot->usr.bit_input[n / 8] & (1 << (n % 8))) {
32806d917491SHans Petter Selasky 			if (!i--)
32813a3f90c6SAndrew Thompson 				return (root + n);
32823a3f90c6SAndrew Thompson 		}
32833a3f90c6SAndrew Thompson 	} while (n--);
32843a3f90c6SAndrew Thompson 
32853a3f90c6SAndrew Thompson 	return (NULL);
32863a3f90c6SAndrew Thompson }
32873a3f90c6SAndrew Thompson 
32886d917491SHans Petter Selasky static const struct uaudio_terminal_node *
32896d917491SHans Petter Selasky uaudio_mixer_get_output(const struct uaudio_terminal_node *iot, uint8_t i)
32903a3f90c6SAndrew Thompson {
32913a3f90c6SAndrew Thompson 	struct uaudio_terminal_node *root = iot->root;
32923a3f90c6SAndrew Thompson 	uint8_t n;
32933a3f90c6SAndrew Thompson 
32943a3f90c6SAndrew Thompson 	n = iot->usr.id_max;
32953a3f90c6SAndrew Thompson 	do {
32963a3f90c6SAndrew Thompson 		if (iot->usr.bit_output[n / 8] & (1 << (n % 8))) {
32976d917491SHans Petter Selasky 			if (!i--)
32983a3f90c6SAndrew Thompson 				return (root + n);
32993a3f90c6SAndrew Thompson 		}
33003a3f90c6SAndrew Thompson 	} while (n--);
33013a3f90c6SAndrew Thompson 
33023a3f90c6SAndrew Thompson 	return (NULL);
33033a3f90c6SAndrew Thompson }
33043a3f90c6SAndrew Thompson 
33053a3f90c6SAndrew Thompson static void
33063a3f90c6SAndrew Thompson uaudio_mixer_find_inputs_sub(struct uaudio_terminal_node *root,
33073a3f90c6SAndrew Thompson     const uint8_t *p_id, uint8_t n_id,
33083a3f90c6SAndrew Thompson     struct uaudio_search_result *info)
33093a3f90c6SAndrew Thompson {
33103a3f90c6SAndrew Thompson 	struct uaudio_terminal_node *iot;
33113a3f90c6SAndrew Thompson 	uint8_t n;
33123a3f90c6SAndrew Thompson 	uint8_t i;
3313e2524b2eSHans Petter Selasky 	uint8_t is_last;
33143a3f90c6SAndrew Thompson 
3315e2524b2eSHans Petter Selasky top:
33163a3f90c6SAndrew Thompson 	for (n = 0; n < n_id; n++) {
33173a3f90c6SAndrew Thompson 
33183a3f90c6SAndrew Thompson 		i = p_id[n];
33193a3f90c6SAndrew Thompson 
3320e2524b2eSHans Petter Selasky 		if (info->recurse_level == UAUDIO_RECURSE_LIMIT) {
33213a3f90c6SAndrew Thompson 			DPRINTF("avoided going into a circle at id=%d!\n", i);
3322e2524b2eSHans Petter Selasky 			return;
33233a3f90c6SAndrew Thompson 		}
33243a3f90c6SAndrew Thompson 
3325e2524b2eSHans Petter Selasky 		info->recurse_level++;
3326e2524b2eSHans Petter Selasky 
33273a3f90c6SAndrew Thompson 		iot = (root + i);
33283a3f90c6SAndrew Thompson 
3329e2524b2eSHans Petter Selasky 		if (iot->u.desc == NULL)
33303a3f90c6SAndrew Thompson 			continue;
3331e2524b2eSHans Petter Selasky 
3332e2524b2eSHans Petter Selasky 		is_last = ((n + 1) == n_id);
3333e2524b2eSHans Petter Selasky 
33343a3f90c6SAndrew Thompson 		switch (iot->u.desc->bDescriptorSubtype) {
33353a3f90c6SAndrew Thompson 		case UDESCSUB_AC_INPUT:
33363a3f90c6SAndrew Thompson 			info->bit_input[i / 8] |= (1 << (i % 8));
33373a3f90c6SAndrew Thompson 			break;
33383a3f90c6SAndrew Thompson 
33393a3f90c6SAndrew Thompson 		case UDESCSUB_AC_FEATURE:
3340e2524b2eSHans Petter Selasky 			if (is_last) {
3341e2524b2eSHans Petter Selasky 				p_id = &iot->u.fu_v1->bSourceId;
3342e2524b2eSHans Petter Selasky 				n_id = 1;
3343e2524b2eSHans Petter Selasky 				goto top;
3344e2524b2eSHans Petter Selasky 			}
3345e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
3346e2524b2eSHans Petter Selasky 			    root, &iot->u.fu_v1->bSourceId, 1, info);
33473a3f90c6SAndrew Thompson 			break;
33483a3f90c6SAndrew Thompson 
33493a3f90c6SAndrew Thompson 		case UDESCSUB_AC_OUTPUT:
3350e2524b2eSHans Petter Selasky 			if (is_last) {
3351e2524b2eSHans Petter Selasky 				p_id = &iot->u.ot_v1->bSourceId;
3352e2524b2eSHans Petter Selasky 				n_id = 1;
3353e2524b2eSHans Petter Selasky 				goto top;
3354e2524b2eSHans Petter Selasky 			}
3355e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
3356e2524b2eSHans Petter Selasky 			    root, &iot->u.ot_v1->bSourceId, 1, info);
33573a3f90c6SAndrew Thompson 			break;
33583a3f90c6SAndrew Thompson 
33593a3f90c6SAndrew Thompson 		case UDESCSUB_AC_MIXER:
3360e2524b2eSHans Petter Selasky 			if (is_last) {
3361e2524b2eSHans Petter Selasky 				p_id = iot->u.mu_v1->baSourceId;
3362e2524b2eSHans Petter Selasky 				n_id = iot->u.mu_v1->bNrInPins;
3363e2524b2eSHans Petter Selasky 				goto top;
3364e2524b2eSHans Petter Selasky 			}
3365e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
3366e2524b2eSHans Petter Selasky 			    root, iot->u.mu_v1->baSourceId,
3367e2524b2eSHans Petter Selasky 			    iot->u.mu_v1->bNrInPins, info);
33683a3f90c6SAndrew Thompson 			break;
33693a3f90c6SAndrew Thompson 
33703a3f90c6SAndrew Thompson 		case UDESCSUB_AC_SELECTOR:
3371e2524b2eSHans Petter Selasky 			if (is_last) {
3372e2524b2eSHans Petter Selasky 				p_id = iot->u.su_v1->baSourceId;
3373e2524b2eSHans Petter Selasky 				n_id = iot->u.su_v1->bNrInPins;
3374e2524b2eSHans Petter Selasky 				goto top;
3375e2524b2eSHans Petter Selasky 			}
3376e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
3377e2524b2eSHans Petter Selasky 			    root, iot->u.su_v1->baSourceId,
3378e2524b2eSHans Petter Selasky 			    iot->u.su_v1->bNrInPins, info);
33793a3f90c6SAndrew Thompson 			break;
33803a3f90c6SAndrew Thompson 
33813a3f90c6SAndrew Thompson 		case UDESCSUB_AC_PROCESSING:
3382e2524b2eSHans Petter Selasky 			if (is_last) {
3383e2524b2eSHans Petter Selasky 				p_id = iot->u.pu_v1->baSourceId;
3384e2524b2eSHans Petter Selasky 				n_id = iot->u.pu_v1->bNrInPins;
3385e2524b2eSHans Petter Selasky 				goto top;
3386e2524b2eSHans Petter Selasky 			}
3387e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
3388e2524b2eSHans Petter Selasky 			    root, iot->u.pu_v1->baSourceId,
3389e2524b2eSHans Petter Selasky 			    iot->u.pu_v1->bNrInPins, info);
33903a3f90c6SAndrew Thompson 			break;
33913a3f90c6SAndrew Thompson 
33923a3f90c6SAndrew Thompson 		case UDESCSUB_AC_EXTENSION:
3393e2524b2eSHans Petter Selasky 			if (is_last) {
3394e2524b2eSHans Petter Selasky 				p_id = iot->u.eu_v1->baSourceId;
3395e2524b2eSHans Petter Selasky 				n_id = iot->u.eu_v1->bNrInPins;
3396e2524b2eSHans Petter Selasky 				goto top;
3397e2524b2eSHans Petter Selasky 			}
3398e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
3399e2524b2eSHans Petter Selasky 			    root, iot->u.eu_v1->baSourceId,
3400e2524b2eSHans Petter Selasky 			    iot->u.eu_v1->bNrInPins, info);
34013a3f90c6SAndrew Thompson 			break;
34023a3f90c6SAndrew Thompson 
34033a3f90c6SAndrew Thompson 		default:
34043a3f90c6SAndrew Thompson 			break;
34053a3f90c6SAndrew Thompson 		}
34063a3f90c6SAndrew Thompson 	}
3407e2524b2eSHans Petter Selasky }
3408e2524b2eSHans Petter Selasky 
3409e2524b2eSHans Petter Selasky static void
3410e2524b2eSHans Petter Selasky uaudio20_mixer_find_inputs_sub(struct uaudio_terminal_node *root,
3411e2524b2eSHans Petter Selasky     const uint8_t *p_id, uint8_t n_id,
3412e2524b2eSHans Petter Selasky     struct uaudio_search_result *info)
3413e2524b2eSHans Petter Selasky {
3414e2524b2eSHans Petter Selasky 	struct uaudio_terminal_node *iot;
3415e2524b2eSHans Petter Selasky 	uint8_t n;
3416e2524b2eSHans Petter Selasky 	uint8_t i;
3417e2524b2eSHans Petter Selasky 	uint8_t is_last;
3418e2524b2eSHans Petter Selasky 
3419e2524b2eSHans Petter Selasky top:
3420e2524b2eSHans Petter Selasky 	for (n = 0; n < n_id; n++) {
3421e2524b2eSHans Petter Selasky 
3422e2524b2eSHans Petter Selasky 		i = p_id[n];
3423e2524b2eSHans Petter Selasky 
3424e2524b2eSHans Petter Selasky 		if (info->recurse_level == UAUDIO_RECURSE_LIMIT) {
3425e2524b2eSHans Petter Selasky 			DPRINTF("avoided going into a circle at id=%d!\n", i);
3426e2524b2eSHans Petter Selasky 			return;
3427e2524b2eSHans Petter Selasky 		}
3428e2524b2eSHans Petter Selasky 
3429e2524b2eSHans Petter Selasky 		info->recurse_level++;
3430e2524b2eSHans Petter Selasky 
3431e2524b2eSHans Petter Selasky 		iot = (root + i);
3432e2524b2eSHans Petter Selasky 
3433e2524b2eSHans Petter Selasky 		if (iot->u.desc == NULL)
3434e2524b2eSHans Petter Selasky 			continue;
3435e2524b2eSHans Petter Selasky 
3436e2524b2eSHans Petter Selasky 		is_last = ((n + 1) == n_id);
3437e2524b2eSHans Petter Selasky 
3438e2524b2eSHans Petter Selasky 		switch (iot->u.desc->bDescriptorSubtype) {
3439e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_INPUT:
3440e2524b2eSHans Petter Selasky 			info->bit_input[i / 8] |= (1 << (i % 8));
3441e2524b2eSHans Petter Selasky 			break;
3442e2524b2eSHans Petter Selasky 
3443e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_OUTPUT:
3444e2524b2eSHans Petter Selasky 			if (is_last) {
3445e2524b2eSHans Petter Selasky 				p_id = &iot->u.ot_v2->bSourceId;
3446e2524b2eSHans Petter Selasky 				n_id = 1;
3447e2524b2eSHans Petter Selasky 				goto top;
3448e2524b2eSHans Petter Selasky 			}
3449e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
3450e2524b2eSHans Petter Selasky 			    root, &iot->u.ot_v2->bSourceId, 1, info);
3451e2524b2eSHans Petter Selasky 			break;
3452e2524b2eSHans Petter Selasky 
3453e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_MIXER:
3454e2524b2eSHans Petter Selasky 			if (is_last) {
3455e2524b2eSHans Petter Selasky 				p_id = iot->u.mu_v2->baSourceId;
3456e2524b2eSHans Petter Selasky 				n_id = iot->u.mu_v2->bNrInPins;
3457e2524b2eSHans Petter Selasky 				goto top;
3458e2524b2eSHans Petter Selasky 			}
3459e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
3460e2524b2eSHans Petter Selasky 			    root, iot->u.mu_v2->baSourceId,
3461e2524b2eSHans Petter Selasky 			    iot->u.mu_v2->bNrInPins, info);
3462e2524b2eSHans Petter Selasky 			break;
3463e2524b2eSHans Petter Selasky 
3464e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_SELECTOR:
3465e2524b2eSHans Petter Selasky 			if (is_last) {
3466e2524b2eSHans Petter Selasky 				p_id = iot->u.su_v2->baSourceId;
3467e2524b2eSHans Petter Selasky 				n_id = iot->u.su_v2->bNrInPins;
3468e2524b2eSHans Petter Selasky 				goto top;
3469e2524b2eSHans Petter Selasky 			}
3470e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
3471e2524b2eSHans Petter Selasky 			    root, iot->u.su_v2->baSourceId,
3472e2524b2eSHans Petter Selasky 			    iot->u.su_v2->bNrInPins, info);
3473e2524b2eSHans Petter Selasky 			break;
3474e2524b2eSHans Petter Selasky 
3475e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_SAMPLE_RT:
3476e2524b2eSHans Petter Selasky 			if (is_last) {
3477e2524b2eSHans Petter Selasky 				p_id = &iot->u.ru_v2->bSourceId;
3478e2524b2eSHans Petter Selasky 				n_id = 1;
3479e2524b2eSHans Petter Selasky 				goto top;
3480e2524b2eSHans Petter Selasky 			}
3481e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
3482e2524b2eSHans Petter Selasky 			    root, &iot->u.ru_v2->bSourceId,
3483e2524b2eSHans Petter Selasky 			    1, info);
3484e2524b2eSHans Petter Selasky 			break;
3485e2524b2eSHans Petter Selasky 
3486e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_EFFECT:
3487e2524b2eSHans Petter Selasky 			if (is_last) {
3488e2524b2eSHans Petter Selasky 				p_id = &iot->u.ef_v2->bSourceId;
3489e2524b2eSHans Petter Selasky 				n_id = 1;
3490e2524b2eSHans Petter Selasky 				goto top;
3491e2524b2eSHans Petter Selasky 			}
3492e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
3493e2524b2eSHans Petter Selasky 			    root, &iot->u.ef_v2->bSourceId,
3494e2524b2eSHans Petter Selasky 			    1, info);
3495e2524b2eSHans Petter Selasky 			break;
3496e2524b2eSHans Petter Selasky 
3497e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_FEATURE:
3498e2524b2eSHans Petter Selasky 			if (is_last) {
3499e2524b2eSHans Petter Selasky 				p_id = &iot->u.fu_v2->bSourceId;
3500e2524b2eSHans Petter Selasky 				n_id = 1;
3501e2524b2eSHans Petter Selasky 				goto top;
3502e2524b2eSHans Petter Selasky 			}
3503e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
3504e2524b2eSHans Petter Selasky 			    root, &iot->u.fu_v2->bSourceId, 1, info);
3505e2524b2eSHans Petter Selasky 			break;
3506e2524b2eSHans Petter Selasky 
3507e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_PROCESSING_V2:
3508e2524b2eSHans Petter Selasky 			if (is_last) {
3509e2524b2eSHans Petter Selasky 				p_id = iot->u.pu_v2->baSourceId;
3510e2524b2eSHans Petter Selasky 				n_id = iot->u.pu_v2->bNrInPins;
3511e2524b2eSHans Petter Selasky 				goto top;
3512e2524b2eSHans Petter Selasky 			}
3513e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
3514e2524b2eSHans Petter Selasky 			    root, iot->u.pu_v2->baSourceId,
3515e2524b2eSHans Petter Selasky 			    iot->u.pu_v2->bNrInPins, info);
3516e2524b2eSHans Petter Selasky 			break;
3517e2524b2eSHans Petter Selasky 
3518e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_EXTENSION_V2:
3519e2524b2eSHans Petter Selasky 			if (is_last) {
3520e2524b2eSHans Petter Selasky 				p_id = iot->u.eu_v2->baSourceId;
3521e2524b2eSHans Petter Selasky 				n_id = iot->u.eu_v2->bNrInPins;
3522e2524b2eSHans Petter Selasky 				goto top;
3523e2524b2eSHans Petter Selasky 			}
3524e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
3525e2524b2eSHans Petter Selasky 			    root, iot->u.eu_v2->baSourceId,
3526e2524b2eSHans Petter Selasky 			    iot->u.eu_v2->bNrInPins, info);
3527e2524b2eSHans Petter Selasky 			break;
3528e2524b2eSHans Petter Selasky 		default:
3529e2524b2eSHans Petter Selasky 			break;
3530e2524b2eSHans Petter Selasky 		}
3531e2524b2eSHans Petter Selasky 	}
3532e2524b2eSHans Petter Selasky }
3533e2524b2eSHans Petter Selasky 
3534e2524b2eSHans Petter Selasky static void
3535e2524b2eSHans Petter Selasky uaudio20_mixer_find_clocks_sub(struct uaudio_terminal_node *root,
3536e2524b2eSHans Petter Selasky     const uint8_t *p_id, uint8_t n_id,
3537e2524b2eSHans Petter Selasky     struct uaudio_search_result *info)
3538e2524b2eSHans Petter Selasky {
3539e2524b2eSHans Petter Selasky 	struct uaudio_terminal_node *iot;
3540e2524b2eSHans Petter Selasky 	uint8_t n;
3541e2524b2eSHans Petter Selasky 	uint8_t i;
3542e2524b2eSHans Petter Selasky 	uint8_t is_last;
3543e2524b2eSHans Petter Selasky 	uint8_t id;
3544e2524b2eSHans Petter Selasky 
3545e2524b2eSHans Petter Selasky top:
3546e2524b2eSHans Petter Selasky 	for (n = 0; n < n_id; n++) {
3547e2524b2eSHans Petter Selasky 
3548e2524b2eSHans Petter Selasky 		i = p_id[n];
3549e2524b2eSHans Petter Selasky 
3550e2524b2eSHans Petter Selasky 		if (info->recurse_level == UAUDIO_RECURSE_LIMIT) {
3551e2524b2eSHans Petter Selasky 			DPRINTF("avoided going into a circle at id=%d!\n", i);
3552e2524b2eSHans Petter Selasky 			return;
3553e2524b2eSHans Petter Selasky 		}
3554e2524b2eSHans Petter Selasky 
3555e2524b2eSHans Petter Selasky 		info->recurse_level++;
3556e2524b2eSHans Petter Selasky 
3557e2524b2eSHans Petter Selasky 		iot = (root + i);
3558e2524b2eSHans Petter Selasky 
3559e2524b2eSHans Petter Selasky 		if (iot->u.desc == NULL)
3560e2524b2eSHans Petter Selasky 			continue;
3561e2524b2eSHans Petter Selasky 
3562e2524b2eSHans Petter Selasky 		is_last = ((n + 1) == n_id);
3563e2524b2eSHans Petter Selasky 
3564e2524b2eSHans Petter Selasky 		switch (iot->u.desc->bDescriptorSubtype) {
3565e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_INPUT:
3566e2524b2eSHans Petter Selasky 			info->is_input = 1;
3567e2524b2eSHans Petter Selasky 			if (is_last) {
3568e2524b2eSHans Petter Selasky 				p_id = &iot->u.it_v2->bCSourceId;
3569e2524b2eSHans Petter Selasky 				n_id = 1;
3570e2524b2eSHans Petter Selasky 				goto top;
3571e2524b2eSHans Petter Selasky 			}
3572e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_clocks_sub(root,
3573e2524b2eSHans Petter Selasky 			    &iot->u.it_v2->bCSourceId, 1, info);
3574e2524b2eSHans Petter Selasky 			break;
3575e2524b2eSHans Petter Selasky 
3576e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_OUTPUT:
3577e2524b2eSHans Petter Selasky 			info->is_input = 0;
3578e2524b2eSHans Petter Selasky 			if (is_last) {
3579e2524b2eSHans Petter Selasky 				p_id = &iot->u.ot_v2->bCSourceId;
3580e2524b2eSHans Petter Selasky 				n_id = 1;
3581e2524b2eSHans Petter Selasky 				goto top;
3582e2524b2eSHans Petter Selasky 			}
3583e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_clocks_sub(root,
3584e2524b2eSHans Petter Selasky 			    &iot->u.ot_v2->bCSourceId, 1, info);
3585e2524b2eSHans Petter Selasky 			break;
3586e2524b2eSHans Petter Selasky 
3587e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_CLOCK_SEL:
3588e2524b2eSHans Petter Selasky 			if (is_last) {
3589e2524b2eSHans Petter Selasky 				p_id = iot->u.csel_v2->baCSourceId;
3590e2524b2eSHans Petter Selasky 				n_id = iot->u.csel_v2->bNrInPins;
3591e2524b2eSHans Petter Selasky 				goto top;
3592e2524b2eSHans Petter Selasky 			}
3593e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_clocks_sub(root,
3594e2524b2eSHans Petter Selasky 			    iot->u.csel_v2->baCSourceId,
3595e2524b2eSHans Petter Selasky 			    iot->u.csel_v2->bNrInPins, info);
3596e2524b2eSHans Petter Selasky 			break;
3597e2524b2eSHans Petter Selasky 
3598e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_CLOCK_MUL:
3599e2524b2eSHans Petter Selasky 			if (is_last) {
3600e2524b2eSHans Petter Selasky 				p_id = &iot->u.cmul_v2->bCSourceId;
3601e2524b2eSHans Petter Selasky 				n_id = 1;
3602e2524b2eSHans Petter Selasky 				goto top;
3603e2524b2eSHans Petter Selasky 			}
3604e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_clocks_sub(root,
3605e2524b2eSHans Petter Selasky 			    &iot->u.cmul_v2->bCSourceId,
3606e2524b2eSHans Petter Selasky 			    1, info);
3607e2524b2eSHans Petter Selasky 			break;
3608e2524b2eSHans Petter Selasky 
3609e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_CLOCK_SRC:
3610e2524b2eSHans Petter Selasky 
3611e2524b2eSHans Petter Selasky 			id = iot->u.csrc_v2->bClockId;
3612e2524b2eSHans Petter Selasky 
3613e2524b2eSHans Petter Selasky 			switch (info->is_input) {
3614e2524b2eSHans Petter Selasky 			case 0:
3615e2524b2eSHans Petter Selasky 				info->bit_output[id / 8] |= (1 << (id % 8));
3616e2524b2eSHans Petter Selasky 				break;
3617e2524b2eSHans Petter Selasky 			case 1:
3618e2524b2eSHans Petter Selasky 				info->bit_input[id / 8] |= (1 << (id % 8));
3619e2524b2eSHans Petter Selasky 				break;
3620e2524b2eSHans Petter Selasky 			default:
3621e2524b2eSHans Petter Selasky 				break;
3622e2524b2eSHans Petter Selasky 			}
3623e2524b2eSHans Petter Selasky 			break;
3624e2524b2eSHans Petter Selasky 
3625e2524b2eSHans Petter Selasky 		default:
3626e2524b2eSHans Petter Selasky 			break;
3627e2524b2eSHans Petter Selasky 		}
3628e2524b2eSHans Petter Selasky 	}
36293a3f90c6SAndrew Thompson }
36303a3f90c6SAndrew Thompson 
36313a3f90c6SAndrew Thompson static void
36323a3f90c6SAndrew Thompson uaudio_mixer_find_outputs_sub(struct uaudio_terminal_node *root, uint8_t id,
36333a3f90c6SAndrew Thompson     uint8_t n_id, struct uaudio_search_result *info)
36343a3f90c6SAndrew Thompson {
36353a3f90c6SAndrew Thompson 	struct uaudio_terminal_node *iot = (root + id);
36363a3f90c6SAndrew Thompson 	uint8_t j;
36373a3f90c6SAndrew Thompson 
36383a3f90c6SAndrew Thompson 	j = n_id;
36393a3f90c6SAndrew Thompson 	do {
36403a3f90c6SAndrew Thompson 		if ((j != id) && ((root + j)->u.desc) &&
36413a3f90c6SAndrew Thompson 		    ((root + j)->u.desc->bDescriptorSubtype == UDESCSUB_AC_OUTPUT)) {
36423a3f90c6SAndrew Thompson 
36433a3f90c6SAndrew Thompson 			/*
36443a3f90c6SAndrew Thompson 			 * "j" (output) <--- virtual wire <--- "id" (input)
36453a3f90c6SAndrew Thompson 			 *
36463a3f90c6SAndrew Thompson 			 * if "j" has "id" on the input, then "id" have "j" on
36473a3f90c6SAndrew Thompson 			 * the output, because they are connected:
36483a3f90c6SAndrew Thompson 			 */
36493a3f90c6SAndrew Thompson 			if ((root + j)->usr.bit_input[id / 8] & (1 << (id % 8))) {
36503a3f90c6SAndrew Thompson 				iot->usr.bit_output[j / 8] |= (1 << (j % 8));
36513a3f90c6SAndrew Thompson 			}
36523a3f90c6SAndrew Thompson 		}
36533a3f90c6SAndrew Thompson 	} while (j--);
36543a3f90c6SAndrew Thompson }
36553a3f90c6SAndrew Thompson 
36563a3f90c6SAndrew Thompson static void
3657e2524b2eSHans Petter Selasky uaudio_mixer_fill_info(struct uaudio_softc *sc,
3658e2524b2eSHans Petter Selasky     struct usb_device *udev, void *desc)
36593a3f90c6SAndrew Thompson {
36604c21be9bSRebecca Cran 	const struct usb_audio_control_descriptor *acdp;
3661a593f6b8SAndrew Thompson 	struct usb_config_descriptor *cd = usbd_get_config_descriptor(udev);
3662760bc48eSAndrew Thompson 	const struct usb_descriptor *dp;
36634c21be9bSRebecca Cran 	const struct usb_audio_unit *au;
36643a3f90c6SAndrew Thompson 	struct uaudio_terminal_node *iot = NULL;
36653a3f90c6SAndrew Thompson 	uint16_t wTotalLen;
36663a3f90c6SAndrew Thompson 	uint8_t ID_max = 0;		/* inclusive */
36673a3f90c6SAndrew Thompson 	uint8_t i;
36683a3f90c6SAndrew Thompson 
3669a593f6b8SAndrew Thompson 	desc = usb_desc_foreach(cd, desc);
36703a3f90c6SAndrew Thompson 
36713a3f90c6SAndrew Thompson 	if (desc == NULL) {
36723a3f90c6SAndrew Thompson 		DPRINTF("no Audio Control header\n");
36733a3f90c6SAndrew Thompson 		goto done;
36743a3f90c6SAndrew Thompson 	}
36753a3f90c6SAndrew Thompson 	acdp = desc;
36763a3f90c6SAndrew Thompson 
36773a3f90c6SAndrew Thompson 	if ((acdp->bLength < sizeof(*acdp)) ||
36783a3f90c6SAndrew Thompson 	    (acdp->bDescriptorType != UDESC_CS_INTERFACE) ||
36793a3f90c6SAndrew Thompson 	    (acdp->bDescriptorSubtype != UDESCSUB_AC_HEADER)) {
36803a3f90c6SAndrew Thompson 		DPRINTF("invalid Audio Control header\n");
36813a3f90c6SAndrew Thompson 		goto done;
36823a3f90c6SAndrew Thompson 	}
36833a3f90c6SAndrew Thompson 	/* "wTotalLen" is allowed to be corrupt */
36843a3f90c6SAndrew Thompson 	wTotalLen = UGETW(acdp->wTotalLength) - acdp->bLength;
36853a3f90c6SAndrew Thompson 
36863a3f90c6SAndrew Thompson 	/* get USB audio revision */
36873a3f90c6SAndrew Thompson 	sc->sc_audio_rev = UGETW(acdp->bcdADC);
36883a3f90c6SAndrew Thompson 
36893a3f90c6SAndrew Thompson 	DPRINTFN(3, "found AC header, vers=%03x, len=%d\n",
36903a3f90c6SAndrew Thompson 	    sc->sc_audio_rev, wTotalLen);
36913a3f90c6SAndrew Thompson 
36923a3f90c6SAndrew Thompson 	iot = malloc(sizeof(struct uaudio_terminal_node) * 256, M_TEMP,
36933a3f90c6SAndrew Thompson 	    M_WAITOK | M_ZERO);
36943a3f90c6SAndrew Thompson 
36953a3f90c6SAndrew Thompson 	if (iot == NULL) {
36963a3f90c6SAndrew Thompson 		DPRINTF("no memory!\n");
36973a3f90c6SAndrew Thompson 		goto done;
36983a3f90c6SAndrew Thompson 	}
3699a593f6b8SAndrew Thompson 	while ((desc = usb_desc_foreach(cd, desc))) {
37003a3f90c6SAndrew Thompson 
37013a3f90c6SAndrew Thompson 		dp = desc;
37023a3f90c6SAndrew Thompson 
37033a3f90c6SAndrew Thompson 		if (dp->bLength > wTotalLen) {
37043a3f90c6SAndrew Thompson 			break;
37053a3f90c6SAndrew Thompson 		} else {
37063a3f90c6SAndrew Thompson 			wTotalLen -= dp->bLength;
37073a3f90c6SAndrew Thompson 		}
37083a3f90c6SAndrew Thompson 
3709e2524b2eSHans Petter Selasky 		if (sc->sc_audio_rev >= UAUDIO_VERSION_30)
3710e2524b2eSHans Petter Selasky 			au = NULL;
3711e2524b2eSHans Petter Selasky 		else if (sc->sc_audio_rev >= UAUDIO_VERSION_20)
3712e2524b2eSHans Petter Selasky 			au = uaudio20_mixer_verify_desc(dp, 0);
3713e2524b2eSHans Petter Selasky 		else
37143a3f90c6SAndrew Thompson 			au = uaudio_mixer_verify_desc(dp, 0);
37153a3f90c6SAndrew Thompson 
37163a3f90c6SAndrew Thompson 		if (au) {
37173a3f90c6SAndrew Thompson 			iot[au->bUnitId].u.desc = (const void *)au;
3718e2524b2eSHans Petter Selasky 			if (au->bUnitId > ID_max)
37193a3f90c6SAndrew Thompson 				ID_max = au->bUnitId;
37203a3f90c6SAndrew Thompson 		}
37213a3f90c6SAndrew Thompson 	}
37223a3f90c6SAndrew Thompson 
37233a3f90c6SAndrew Thompson 	DPRINTF("Maximum ID=%d\n", ID_max);
37243a3f90c6SAndrew Thompson 
37253a3f90c6SAndrew Thompson 	/*
37263a3f90c6SAndrew Thompson 	 * determine sourcing inputs for
37273a3f90c6SAndrew Thompson 	 * all nodes in the tree:
37283a3f90c6SAndrew Thompson 	 */
37293a3f90c6SAndrew Thompson 	i = ID_max;
37303a3f90c6SAndrew Thompson 	do {
3731e2524b2eSHans Petter Selasky 		if (sc->sc_audio_rev >= UAUDIO_VERSION_30) {
3732e2524b2eSHans Petter Selasky 			/* FALLTHROUGH */
3733e2524b2eSHans Petter Selasky 		} else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) {
3734e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(iot,
3735e2524b2eSHans Petter Selasky 			    &i, 1, &((iot + i)->usr));
3736e2524b2eSHans Petter Selasky 
3737e2524b2eSHans Petter Selasky 			sc->sc_mixer_clocks.is_input = 255;
3738e2524b2eSHans Petter Selasky 			sc->sc_mixer_clocks.recurse_level = 0;
3739e2524b2eSHans Petter Selasky 
3740e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_clocks_sub(iot,
3741e2524b2eSHans Petter Selasky 			    &i, 1, &sc->sc_mixer_clocks);
3742e2524b2eSHans Petter Selasky 		} else {
3743e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(iot,
3744e2524b2eSHans Petter Selasky 			    &i, 1, &((iot + i)->usr));
3745e2524b2eSHans Petter Selasky 		}
37463a3f90c6SAndrew Thompson 	} while (i--);
37473a3f90c6SAndrew Thompson 
37483a3f90c6SAndrew Thompson 	/*
37493a3f90c6SAndrew Thompson 	 * determine outputs for
37503a3f90c6SAndrew Thompson 	 * all nodes in the tree:
37513a3f90c6SAndrew Thompson 	 */
37523a3f90c6SAndrew Thompson 	i = ID_max;
37533a3f90c6SAndrew Thompson 	do {
3754e2524b2eSHans Petter Selasky 		uaudio_mixer_find_outputs_sub(iot,
3755e2524b2eSHans Petter Selasky 		    i, ID_max, &((iot + i)->usr));
37563a3f90c6SAndrew Thompson 	} while (i--);
37573a3f90c6SAndrew Thompson 
37583a3f90c6SAndrew Thompson 	/* set "id_max" and "root" */
37593a3f90c6SAndrew Thompson 
37603a3f90c6SAndrew Thompson 	i = ID_max;
37613a3f90c6SAndrew Thompson 	do {
37623a3f90c6SAndrew Thompson 		(iot + i)->usr.id_max = ID_max;
37633a3f90c6SAndrew Thompson 		(iot + i)->root = iot;
37643a3f90c6SAndrew Thompson 	} while (i--);
37653a3f90c6SAndrew Thompson 
37663a3f90c6SAndrew Thompson 	/*
3767e2524b2eSHans Petter Selasky 	 * Scan the config to create a linked list of "mixer" nodes:
37683a3f90c6SAndrew Thompson 	 */
37693a3f90c6SAndrew Thompson 
37703a3f90c6SAndrew Thompson 	i = ID_max;
37713a3f90c6SAndrew Thompson 	do {
37723a3f90c6SAndrew Thompson 		dp = iot[i].u.desc;
37733a3f90c6SAndrew Thompson 
3774e2524b2eSHans Petter Selasky 		if (dp == NULL)
37753a3f90c6SAndrew Thompson 			continue;
3776e2524b2eSHans Petter Selasky 
37773a3f90c6SAndrew Thompson 		DPRINTFN(11, "id=%d subtype=%d\n",
37783a3f90c6SAndrew Thompson 		    i, dp->bDescriptorSubtype);
37793a3f90c6SAndrew Thompson 
3780e2524b2eSHans Petter Selasky 		if (sc->sc_audio_rev >= UAUDIO_VERSION_30) {
3781e2524b2eSHans Petter Selasky 			continue;
3782e2524b2eSHans Petter Selasky 		} else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) {
3783e2524b2eSHans Petter Selasky 
37843a3f90c6SAndrew Thompson 			switch (dp->bDescriptorSubtype) {
37853a3f90c6SAndrew Thompson 			case UDESCSUB_AC_HEADER:
37863a3f90c6SAndrew Thompson 				DPRINTF("unexpected AC header\n");
37873a3f90c6SAndrew Thompson 				break;
37883a3f90c6SAndrew Thompson 
37893a3f90c6SAndrew Thompson 			case UDESCSUB_AC_INPUT:
3790e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_OUTPUT:
3791e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_PROCESSING_V2:
3792e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_EXTENSION_V2:
3793e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_EFFECT:
3794e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_CLOCK_SRC:
3795e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_CLOCK_SEL:
3796e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_CLOCK_MUL:
3797e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_SAMPLE_RT:
37983a3f90c6SAndrew Thompson 				break;
37993a3f90c6SAndrew Thompson 
3800e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_MIXER:
3801e2524b2eSHans Petter Selasky 				uaudio20_mixer_add_mixer(sc, iot, i);
3802e2524b2eSHans Petter Selasky 				break;
3803e2524b2eSHans Petter Selasky 
3804e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_SELECTOR:
3805e2524b2eSHans Petter Selasky 				uaudio20_mixer_add_selector(sc, iot, i);
3806e2524b2eSHans Petter Selasky 				break;
3807e2524b2eSHans Petter Selasky 
3808e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_FEATURE:
3809e2524b2eSHans Petter Selasky 				uaudio20_mixer_add_feature(sc, iot, i);
3810e2524b2eSHans Petter Selasky 				break;
3811e2524b2eSHans Petter Selasky 
3812e2524b2eSHans Petter Selasky 			default:
3813e2524b2eSHans Petter Selasky 				DPRINTF("bad AC desc subtype=0x%02x\n",
3814e2524b2eSHans Petter Selasky 				    dp->bDescriptorSubtype);
3815e2524b2eSHans Petter Selasky 				break;
3816e2524b2eSHans Petter Selasky 			}
3817e2524b2eSHans Petter Selasky 			continue;
3818e2524b2eSHans Petter Selasky 		}
3819e2524b2eSHans Petter Selasky 
3820e2524b2eSHans Petter Selasky 		switch (dp->bDescriptorSubtype) {
3821e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_HEADER:
3822e2524b2eSHans Petter Selasky 			DPRINTF("unexpected AC header\n");
3823e2524b2eSHans Petter Selasky 			break;
3824e2524b2eSHans Petter Selasky 
3825e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_INPUT:
38263a3f90c6SAndrew Thompson 		case UDESCSUB_AC_OUTPUT:
38273a3f90c6SAndrew Thompson 			break;
38283a3f90c6SAndrew Thompson 
38293a3f90c6SAndrew Thompson 		case UDESCSUB_AC_MIXER:
38303a3f90c6SAndrew Thompson 			uaudio_mixer_add_mixer(sc, iot, i);
38313a3f90c6SAndrew Thompson 			break;
38323a3f90c6SAndrew Thompson 
38333a3f90c6SAndrew Thompson 		case UDESCSUB_AC_SELECTOR:
38343a3f90c6SAndrew Thompson 			uaudio_mixer_add_selector(sc, iot, i);
38353a3f90c6SAndrew Thompson 			break;
38363a3f90c6SAndrew Thompson 
38373a3f90c6SAndrew Thompson 		case UDESCSUB_AC_FEATURE:
38383a3f90c6SAndrew Thompson 			uaudio_mixer_add_feature(sc, iot, i);
38393a3f90c6SAndrew Thompson 			break;
38403a3f90c6SAndrew Thompson 
38413a3f90c6SAndrew Thompson 		case UDESCSUB_AC_PROCESSING:
38423a3f90c6SAndrew Thompson 			uaudio_mixer_add_processing(sc, iot, i);
38433a3f90c6SAndrew Thompson 			break;
38443a3f90c6SAndrew Thompson 
38453a3f90c6SAndrew Thompson 		case UDESCSUB_AC_EXTENSION:
38463a3f90c6SAndrew Thompson 			uaudio_mixer_add_extension(sc, iot, i);
38473a3f90c6SAndrew Thompson 			break;
38483a3f90c6SAndrew Thompson 
38493a3f90c6SAndrew Thompson 		default:
38503a3f90c6SAndrew Thompson 			DPRINTF("bad AC desc subtype=0x%02x\n",
38513a3f90c6SAndrew Thompson 			    dp->bDescriptorSubtype);
38523a3f90c6SAndrew Thompson 			break;
38533a3f90c6SAndrew Thompson 		}
38543a3f90c6SAndrew Thompson 
38553a3f90c6SAndrew Thompson 	} while (i--);
38563a3f90c6SAndrew Thompson 
38573a3f90c6SAndrew Thompson done:
38583a3f90c6SAndrew Thompson 	free(iot, M_TEMP);
38593a3f90c6SAndrew Thompson }
38603a3f90c6SAndrew Thompson 
3861e2524b2eSHans Petter Selasky static int
3862e2524b2eSHans Petter Selasky uaudio_mixer_get(struct usb_device *udev, uint16_t audio_rev,
3863e2524b2eSHans Petter Selasky     uint8_t what, struct uaudio_mixer_node *mc)
38643a3f90c6SAndrew Thompson {
3865760bc48eSAndrew Thompson 	struct usb_device_request req;
3866e2524b2eSHans Petter Selasky 	int val;
3867e2524b2eSHans Petter Selasky 	uint8_t data[2 + (2 * 3)];
3868e0a69b51SAndrew Thompson 	usb_error_t err;
38693a3f90c6SAndrew Thompson 
3870e2524b2eSHans Petter Selasky 	if (mc->wValue[0] == -1)
38713a3f90c6SAndrew Thompson 		return (0);
3872e2524b2eSHans Petter Selasky 
3873e2524b2eSHans Petter Selasky 	if (audio_rev >= UAUDIO_VERSION_30)
3874e2524b2eSHans Petter Selasky 		return (0);
3875e2524b2eSHans Petter Selasky 	else if (audio_rev >= UAUDIO_VERSION_20) {
3876e2524b2eSHans Petter Selasky 		if (what == GET_CUR) {
3877e2524b2eSHans Petter Selasky 			req.bRequest = UA20_CS_CUR;
3878e2524b2eSHans Petter Selasky 			USETW(req.wLength, 2);
3879e2524b2eSHans Petter Selasky 		} else {
3880e2524b2eSHans Petter Selasky 			req.bRequest = UA20_CS_RANGE;
3881e2524b2eSHans Petter Selasky 			USETW(req.wLength, 8);
38823a3f90c6SAndrew Thompson 		}
3883e2524b2eSHans Petter Selasky 	} else {
3884e2524b2eSHans Petter Selasky 		uint16_t len = MIX_SIZE(mc->type);
3885e2524b2eSHans Petter Selasky 
38863a3f90c6SAndrew Thompson 		req.bRequest = what;
3887e2524b2eSHans Petter Selasky 		USETW(req.wLength, len);
3888e2524b2eSHans Petter Selasky 	}
3889e2524b2eSHans Petter Selasky 
3890e2524b2eSHans Petter Selasky 	req.bmRequestType = UT_READ_CLASS_INTERFACE;
38913a3f90c6SAndrew Thompson 	USETW(req.wValue, mc->wValue[0]);
38923a3f90c6SAndrew Thompson 	USETW(req.wIndex, mc->wIndex);
3893e2524b2eSHans Petter Selasky 
3894e2524b2eSHans Petter Selasky 	memset(data, 0, sizeof(data));
38953a3f90c6SAndrew Thompson 
3896f24b6817SAlfred Perlstein 	err = usbd_do_request(udev, NULL, &req, data);
38973a3f90c6SAndrew Thompson 	if (err) {
3898a593f6b8SAndrew Thompson 		DPRINTF("err=%s\n", usbd_errstr(err));
38993a3f90c6SAndrew Thompson 		return (0);
39003a3f90c6SAndrew Thompson 	}
3901e2524b2eSHans Petter Selasky 
3902e2524b2eSHans Petter Selasky 	if (audio_rev >= UAUDIO_VERSION_30) {
3903e2524b2eSHans Petter Selasky 		val = 0;
3904e2524b2eSHans Petter Selasky 	} else if (audio_rev >= UAUDIO_VERSION_20) {
3905e2524b2eSHans Petter Selasky 		switch (what) {
3906e2524b2eSHans Petter Selasky 		case GET_CUR:
39073a3f90c6SAndrew Thompson 			val = (data[0] | (data[1] << 8));
3908e2524b2eSHans Petter Selasky 			break;
3909e2524b2eSHans Petter Selasky 		case GET_MIN:
3910e2524b2eSHans Petter Selasky 			val = (data[2] | (data[3] << 8));
3911e2524b2eSHans Petter Selasky 			break;
3912e2524b2eSHans Petter Selasky 		case GET_MAX:
3913e2524b2eSHans Petter Selasky 			val = (data[4] | (data[5] << 8));
3914e2524b2eSHans Petter Selasky 			break;
3915e2524b2eSHans Petter Selasky 		case GET_RES:
3916e2524b2eSHans Petter Selasky 			val = (data[6] | (data[7] << 8));
3917e2524b2eSHans Petter Selasky 			break;
3918e2524b2eSHans Petter Selasky 		default:
3919e2524b2eSHans Petter Selasky 			val = 0;
3920e2524b2eSHans Petter Selasky 			break;
3921e2524b2eSHans Petter Selasky 		}
3922e2524b2eSHans Petter Selasky 	} else {
3923e2524b2eSHans Petter Selasky 		val = (data[0] | (data[1] << 8));
3924e2524b2eSHans Petter Selasky 	}
3925e2524b2eSHans Petter Selasky 
3926e2524b2eSHans Petter Selasky 	if (what == GET_CUR || what == GET_MIN || what == GET_MAX)
3927e2524b2eSHans Petter Selasky 		val = uaudio_mixer_signext(mc->type, val);
39283a3f90c6SAndrew Thompson 
39293a3f90c6SAndrew Thompson 	DPRINTFN(3, "val=%d\n", val);
39303a3f90c6SAndrew Thompson 
39313a3f90c6SAndrew Thompson 	return (val);
39323a3f90c6SAndrew Thompson }
39333a3f90c6SAndrew Thompson 
39343a3f90c6SAndrew Thompson static void
3935ed6d949aSAndrew Thompson uaudio_mixer_write_cfg_callback(struct usb_xfer *xfer, usb_error_t error)
39363a3f90c6SAndrew Thompson {
3937760bc48eSAndrew Thompson 	struct usb_device_request req;
3938ed6d949aSAndrew Thompson 	struct uaudio_softc *sc = usbd_xfer_softc(xfer);
39393a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *mc = sc->sc_mixer_curr;
3940ed6d949aSAndrew Thompson 	struct usb_page_cache *pc;
39413a3f90c6SAndrew Thompson 	uint16_t len;
39423a3f90c6SAndrew Thompson 	uint8_t repeat = 1;
39433a3f90c6SAndrew Thompson 	uint8_t update;
39443a3f90c6SAndrew Thompson 	uint8_t chan;
39453a3f90c6SAndrew Thompson 	uint8_t buf[2];
39463a3f90c6SAndrew Thompson 
39473a3f90c6SAndrew Thompson 	DPRINTF("\n");
39483a3f90c6SAndrew Thompson 
39493a3f90c6SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
39503a3f90c6SAndrew Thompson 	case USB_ST_TRANSFERRED:
39513a3f90c6SAndrew Thompson tr_transferred:
39523a3f90c6SAndrew Thompson 	case USB_ST_SETUP:
39533a3f90c6SAndrew Thompson tr_setup:
39543a3f90c6SAndrew Thompson 
39553a3f90c6SAndrew Thompson 		if (mc == NULL) {
39563a3f90c6SAndrew Thompson 			mc = sc->sc_mixer_root;
39573a3f90c6SAndrew Thompson 			sc->sc_mixer_curr = mc;
39583a3f90c6SAndrew Thompson 			sc->sc_mixer_chan = 0;
39593a3f90c6SAndrew Thompson 			repeat = 0;
39603a3f90c6SAndrew Thompson 		}
39613a3f90c6SAndrew Thompson 		while (mc) {
39623a3f90c6SAndrew Thompson 			while (sc->sc_mixer_chan < mc->nchan) {
39633a3f90c6SAndrew Thompson 
39643a3f90c6SAndrew Thompson 				chan = sc->sc_mixer_chan;
39653a3f90c6SAndrew Thompson 
39663a3f90c6SAndrew Thompson 				sc->sc_mixer_chan++;
39673a3f90c6SAndrew Thompson 
39683a3f90c6SAndrew Thompson 				update = ((mc->update[chan / 8] & (1 << (chan % 8))) &&
39693a3f90c6SAndrew Thompson 				    (mc->wValue[chan] != -1));
39703a3f90c6SAndrew Thompson 
39713a3f90c6SAndrew Thompson 				mc->update[chan / 8] &= ~(1 << (chan % 8));
39723a3f90c6SAndrew Thompson 
39733a3f90c6SAndrew Thompson 				if (update) {
39743a3f90c6SAndrew Thompson 
39753a3f90c6SAndrew Thompson 					req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
39763a3f90c6SAndrew Thompson 					USETW(req.wValue, mc->wValue[chan]);
39773a3f90c6SAndrew Thompson 					USETW(req.wIndex, mc->wIndex);
39783a3f90c6SAndrew Thompson 
3979e2524b2eSHans Petter Selasky 					if (sc->sc_audio_rev >= UAUDIO_VERSION_30) {
3980e2524b2eSHans Petter Selasky 						return;
3981e2524b2eSHans Petter Selasky 					} else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) {
3982e2524b2eSHans Petter Selasky 						len = 2;
3983e2524b2eSHans Petter Selasky 						req.bRequest = UA20_CS_CUR;
3984e2524b2eSHans Petter Selasky 						USETW(req.wLength, len);
3985e2524b2eSHans Petter Selasky 					} else {
3986e2524b2eSHans Petter Selasky 						len = MIX_SIZE(mc->type);
3987e2524b2eSHans Petter Selasky 						req.bRequest = SET_CUR;
3988e2524b2eSHans Petter Selasky 						USETW(req.wLength, len);
3989e2524b2eSHans Petter Selasky 					}
3990e2524b2eSHans Petter Selasky 
39913a3f90c6SAndrew Thompson 					buf[0] = (mc->wData[chan] & 0xFF);
39923a3f90c6SAndrew Thompson 					buf[1] = (mc->wData[chan] >> 8) & 0xFF;
3993e2524b2eSHans Petter Selasky 
3994ed6d949aSAndrew Thompson 					pc = usbd_xfer_get_frame(xfer, 0);
3995ed6d949aSAndrew Thompson 					usbd_copy_in(pc, 0, &req, sizeof(req));
3996ed6d949aSAndrew Thompson 					pc = usbd_xfer_get_frame(xfer, 1);
3997ed6d949aSAndrew Thompson 					usbd_copy_in(pc, 0, buf, len);
39983a3f90c6SAndrew Thompson 
3999ed6d949aSAndrew Thompson 					usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
4000ed6d949aSAndrew Thompson 					usbd_xfer_set_frame_len(xfer, 1, len);
4001ed6d949aSAndrew Thompson 					usbd_xfer_set_frames(xfer, len ? 2 : 1);
4002a593f6b8SAndrew Thompson 					usbd_transfer_submit(xfer);
40033a3f90c6SAndrew Thompson 					return;
40043a3f90c6SAndrew Thompson 				}
40053a3f90c6SAndrew Thompson 			}
40063a3f90c6SAndrew Thompson 
40073a3f90c6SAndrew Thompson 			mc = mc->next;
40083a3f90c6SAndrew Thompson 			sc->sc_mixer_curr = mc;
40093a3f90c6SAndrew Thompson 			sc->sc_mixer_chan = 0;
40103a3f90c6SAndrew Thompson 		}
40113a3f90c6SAndrew Thompson 
40123a3f90c6SAndrew Thompson 		if (repeat) {
40133a3f90c6SAndrew Thompson 			goto tr_setup;
40143a3f90c6SAndrew Thompson 		}
40153a3f90c6SAndrew Thompson 		break;
40163a3f90c6SAndrew Thompson 
40173a3f90c6SAndrew Thompson 	default:			/* Error */
4018ed6d949aSAndrew Thompson 		DPRINTF("error=%s\n", usbd_errstr(error));
4019ed6d949aSAndrew Thompson 		if (error == USB_ERR_CANCELLED) {
40203a3f90c6SAndrew Thompson 			/* do nothing - we are detaching */
40213a3f90c6SAndrew Thompson 			break;
40223a3f90c6SAndrew Thompson 		}
40233a3f90c6SAndrew Thompson 		goto tr_transferred;
40243a3f90c6SAndrew Thompson 	}
40253a3f90c6SAndrew Thompson }
40263a3f90c6SAndrew Thompson 
4027e0a69b51SAndrew Thompson static usb_error_t
4028760bc48eSAndrew Thompson uaudio_set_speed(struct usb_device *udev, uint8_t endpt, uint32_t speed)
40293a3f90c6SAndrew Thompson {
4030760bc48eSAndrew Thompson 	struct usb_device_request req;
40313a3f90c6SAndrew Thompson 	uint8_t data[3];
40323a3f90c6SAndrew Thompson 
40333a3f90c6SAndrew Thompson 	DPRINTFN(6, "endpt=%d speed=%u\n", endpt, speed);
40343a3f90c6SAndrew Thompson 
40353a3f90c6SAndrew Thompson 	req.bmRequestType = UT_WRITE_CLASS_ENDPOINT;
40363a3f90c6SAndrew Thompson 	req.bRequest = SET_CUR;
40373a3f90c6SAndrew Thompson 	USETW2(req.wValue, SAMPLING_FREQ_CONTROL, 0);
40383a3f90c6SAndrew Thompson 	USETW(req.wIndex, endpt);
40393a3f90c6SAndrew Thompson 	USETW(req.wLength, 3);
40403a3f90c6SAndrew Thompson 	data[0] = speed;
40413a3f90c6SAndrew Thompson 	data[1] = speed >> 8;
40423a3f90c6SAndrew Thompson 	data[2] = speed >> 16;
40433a3f90c6SAndrew Thompson 
4044f24b6817SAlfred Perlstein 	return (usbd_do_request(udev, NULL, &req, data));
40453a3f90c6SAndrew Thompson }
40463a3f90c6SAndrew Thompson 
4047e2524b2eSHans Petter Selasky static usb_error_t
4048e2524b2eSHans Petter Selasky uaudio20_set_speed(struct usb_device *udev, uint8_t iface_no,
4049e2524b2eSHans Petter Selasky     uint8_t clockid, uint32_t speed)
4050e2524b2eSHans Petter Selasky {
4051e2524b2eSHans Petter Selasky 	struct usb_device_request req;
4052e2524b2eSHans Petter Selasky 	uint8_t data[4];
4053e2524b2eSHans Petter Selasky 
4054e2524b2eSHans Petter Selasky 	DPRINTFN(6, "ifaceno=%d clockid=%d speed=%u\n",
4055e2524b2eSHans Petter Selasky 	    iface_no, clockid, speed);
4056e2524b2eSHans Petter Selasky 
4057e2524b2eSHans Petter Selasky 	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
4058e2524b2eSHans Petter Selasky 	req.bRequest = UA20_CS_CUR;
4059e2524b2eSHans Petter Selasky 	USETW2(req.wValue, UA20_CS_SAM_FREQ_CONTROL, 0);
4060e2524b2eSHans Petter Selasky 	USETW2(req.wIndex, clockid, iface_no);
4061e2524b2eSHans Petter Selasky 	USETW(req.wLength, 4);
4062e2524b2eSHans Petter Selasky 	data[0] = speed;
4063e2524b2eSHans Petter Selasky 	data[1] = speed >> 8;
4064e2524b2eSHans Petter Selasky 	data[2] = speed >> 16;
4065e2524b2eSHans Petter Selasky 	data[3] = speed >> 24;
4066e2524b2eSHans Petter Selasky 
4067e2524b2eSHans Petter Selasky 	return (usbd_do_request(udev, NULL, &req, data));
4068e2524b2eSHans Petter Selasky }
4069e2524b2eSHans Petter Selasky 
40703a3f90c6SAndrew Thompson static int
40713a3f90c6SAndrew Thompson uaudio_mixer_signext(uint8_t type, int val)
40723a3f90c6SAndrew Thompson {
40733a3f90c6SAndrew Thompson 	if (!MIX_UNSIGNED(type)) {
40743a3f90c6SAndrew Thompson 		if (MIX_SIZE(type) == 2) {
40753a3f90c6SAndrew Thompson 			val = (int16_t)val;
40763a3f90c6SAndrew Thompson 		} else {
40773a3f90c6SAndrew Thompson 			val = (int8_t)val;
40783a3f90c6SAndrew Thompson 		}
40793a3f90c6SAndrew Thompson 	}
40803a3f90c6SAndrew Thompson 	return (val);
40813a3f90c6SAndrew Thompson }
40823a3f90c6SAndrew Thompson 
40833a3f90c6SAndrew Thompson static int
40843a3f90c6SAndrew Thompson uaudio_mixer_bsd2value(struct uaudio_mixer_node *mc, int32_t val)
40853a3f90c6SAndrew Thompson {
40863a3f90c6SAndrew Thompson 	if (mc->type == MIX_ON_OFF) {
40873a3f90c6SAndrew Thompson 		val = (val != 0);
40883a3f90c6SAndrew Thompson 	} else if (mc->type == MIX_SELECTOR) {
40893a3f90c6SAndrew Thompson 		if ((val < mc->minval) ||
40903a3f90c6SAndrew Thompson 		    (val > mc->maxval)) {
40913a3f90c6SAndrew Thompson 			val = mc->minval;
40923a3f90c6SAndrew Thompson 		}
40933a3f90c6SAndrew Thompson 	} else {
4094b029f6bbSAndrew Thompson 
4095b029f6bbSAndrew Thompson 		/* compute actual volume */
4096b029f6bbSAndrew Thompson 		val = (val * mc->mul) / 255;
4097b029f6bbSAndrew Thompson 
4098b029f6bbSAndrew Thompson 		/* add lower offset */
4099b029f6bbSAndrew Thompson 		val = val + mc->minval;
4100b029f6bbSAndrew Thompson 
4101b029f6bbSAndrew Thompson 		/* make sure we don't write a value out of range */
4102b029f6bbSAndrew Thompson 		if (val > mc->maxval)
4103b029f6bbSAndrew Thompson 			val = mc->maxval;
4104b029f6bbSAndrew Thompson 		else if (val < mc->minval)
4105b029f6bbSAndrew Thompson 			val = mc->minval;
41063a3f90c6SAndrew Thompson 	}
41073a3f90c6SAndrew Thompson 
41083a3f90c6SAndrew Thompson 	DPRINTFN(6, "type=0x%03x val=%d min=%d max=%d val=%d\n",
41093a3f90c6SAndrew Thompson 	    mc->type, val, mc->minval, mc->maxval, val);
41103a3f90c6SAndrew Thompson 	return (val);
41113a3f90c6SAndrew Thompson }
41123a3f90c6SAndrew Thompson 
41133a3f90c6SAndrew Thompson static void
41143a3f90c6SAndrew Thompson uaudio_mixer_ctl_set(struct uaudio_softc *sc, struct uaudio_mixer_node *mc,
41153a3f90c6SAndrew Thompson     uint8_t chan, int32_t val)
41163a3f90c6SAndrew Thompson {
41173a3f90c6SAndrew Thompson 	val = uaudio_mixer_bsd2value(mc, val);
41183a3f90c6SAndrew Thompson 
41193a3f90c6SAndrew Thompson 	mc->update[chan / 8] |= (1 << (chan % 8));
41203a3f90c6SAndrew Thompson 	mc->wData[chan] = val;
41213a3f90c6SAndrew Thompson 
41223a3f90c6SAndrew Thompson 	/* start the transfer, if not already started */
41233a3f90c6SAndrew Thompson 
4124a593f6b8SAndrew Thompson 	usbd_transfer_start(sc->sc_mixer_xfer[0]);
41253a3f90c6SAndrew Thompson }
41263a3f90c6SAndrew Thompson 
41273a3f90c6SAndrew Thompson static void
41283a3f90c6SAndrew Thompson uaudio_mixer_init(struct uaudio_softc *sc)
41293a3f90c6SAndrew Thompson {
41303a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *mc;
41313a3f90c6SAndrew Thompson 	int32_t i;
41323a3f90c6SAndrew Thompson 
41333a3f90c6SAndrew Thompson 	for (mc = sc->sc_mixer_root; mc;
41343a3f90c6SAndrew Thompson 	    mc = mc->next) {
41353a3f90c6SAndrew Thompson 
41363a3f90c6SAndrew Thompson 		if (mc->ctl != SOUND_MIXER_NRDEVICES) {
41373a3f90c6SAndrew Thompson 			/*
41383a3f90c6SAndrew Thompson 			 * Set device mask bits. See
41393a3f90c6SAndrew Thompson 			 * /usr/include/machine/soundcard.h
41403a3f90c6SAndrew Thompson 			 */
41413a3f90c6SAndrew Thompson 			sc->sc_mix_info |= (1 << mc->ctl);
41423a3f90c6SAndrew Thompson 		}
41433a3f90c6SAndrew Thompson 		if ((mc->ctl == SOUND_MIXER_NRDEVICES) &&
41443a3f90c6SAndrew Thompson 		    (mc->type == MIX_SELECTOR)) {
41453a3f90c6SAndrew Thompson 
41463a3f90c6SAndrew Thompson 			for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) {
41473a3f90c6SAndrew Thompson 				if (mc->slctrtype[i - 1] == SOUND_MIXER_NRDEVICES) {
41483a3f90c6SAndrew Thompson 					continue;
41493a3f90c6SAndrew Thompson 				}
41503a3f90c6SAndrew Thompson 				sc->sc_recsrc_info |= 1 << mc->slctrtype[i - 1];
41513a3f90c6SAndrew Thompson 			}
41523a3f90c6SAndrew Thompson 		}
41533a3f90c6SAndrew Thompson 	}
41543a3f90c6SAndrew Thompson }
41553a3f90c6SAndrew Thompson 
41563a3f90c6SAndrew Thompson int
41573a3f90c6SAndrew Thompson uaudio_mixer_init_sub(struct uaudio_softc *sc, struct snd_mixer *m)
41583a3f90c6SAndrew Thompson {
41593a3f90c6SAndrew Thompson 	DPRINTF("\n");
41603a3f90c6SAndrew Thompson 
4161a593f6b8SAndrew Thompson 	if (usbd_transfer_setup(sc->sc_udev, &sc->sc_mixer_iface_index,
41623a3f90c6SAndrew Thompson 	    sc->sc_mixer_xfer, uaudio_mixer_config, 1, sc,
41633a3f90c6SAndrew Thompson 	    mixer_get_lock(m))) {
41643a3f90c6SAndrew Thompson 		DPRINTFN(0, "could not allocate USB "
41653a3f90c6SAndrew Thompson 		    "transfer for audio mixer!\n");
41663a3f90c6SAndrew Thompson 		return (ENOMEM);
41673a3f90c6SAndrew Thompson 	}
41683a3f90c6SAndrew Thompson 	if (!(sc->sc_mix_info & SOUND_MASK_VOLUME)) {
41693a3f90c6SAndrew Thompson 		mix_setparentchild(m, SOUND_MIXER_VOLUME, SOUND_MASK_PCM);
41703a3f90c6SAndrew Thompson 		mix_setrealdev(m, SOUND_MIXER_VOLUME, SOUND_MIXER_NONE);
41713a3f90c6SAndrew Thompson 	}
41723a3f90c6SAndrew Thompson 	mix_setdevs(m, sc->sc_mix_info);
41733a3f90c6SAndrew Thompson 	mix_setrecdevs(m, sc->sc_recsrc_info);
41743a3f90c6SAndrew Thompson 	return (0);
41753a3f90c6SAndrew Thompson }
41763a3f90c6SAndrew Thompson 
41773a3f90c6SAndrew Thompson int
41783a3f90c6SAndrew Thompson uaudio_mixer_uninit_sub(struct uaudio_softc *sc)
41793a3f90c6SAndrew Thompson {
41803a3f90c6SAndrew Thompson 	DPRINTF("\n");
41813a3f90c6SAndrew Thompson 
4182a593f6b8SAndrew Thompson 	usbd_transfer_unsetup(sc->sc_mixer_xfer, 1);
41833a3f90c6SAndrew Thompson 
41843a3f90c6SAndrew Thompson 	return (0);
41853a3f90c6SAndrew Thompson }
41863a3f90c6SAndrew Thompson 
41873a3f90c6SAndrew Thompson void
41883a3f90c6SAndrew Thompson uaudio_mixer_set(struct uaudio_softc *sc, unsigned type,
41893a3f90c6SAndrew Thompson     unsigned left, unsigned right)
41903a3f90c6SAndrew Thompson {
41913a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *mc;
41923a3f90c6SAndrew Thompson 
41933a3f90c6SAndrew Thompson 	for (mc = sc->sc_mixer_root; mc;
41943a3f90c6SAndrew Thompson 	    mc = mc->next) {
41953a3f90c6SAndrew Thompson 
41963a3f90c6SAndrew Thompson 		if (mc->ctl == type) {
41973a3f90c6SAndrew Thompson 			if (mc->nchan == 2) {
41983a3f90c6SAndrew Thompson 				/* set Right */
41993a3f90c6SAndrew Thompson 				uaudio_mixer_ctl_set(sc, mc, 1, (int)(right * 255) / 100);
42003a3f90c6SAndrew Thompson 			}
42013a3f90c6SAndrew Thompson 			/* set Left or Mono */
42023a3f90c6SAndrew Thompson 			uaudio_mixer_ctl_set(sc, mc, 0, (int)(left * 255) / 100);
42033a3f90c6SAndrew Thompson 		}
42043a3f90c6SAndrew Thompson 	}
42053a3f90c6SAndrew Thompson }
42063a3f90c6SAndrew Thompson 
42073a3f90c6SAndrew Thompson uint32_t
42083a3f90c6SAndrew Thompson uaudio_mixer_setrecsrc(struct uaudio_softc *sc, uint32_t src)
42093a3f90c6SAndrew Thompson {
42103a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *mc;
42113a3f90c6SAndrew Thompson 	uint32_t mask;
42123a3f90c6SAndrew Thompson 	uint32_t temp;
42133a3f90c6SAndrew Thompson 	int32_t i;
42143a3f90c6SAndrew Thompson 
42153a3f90c6SAndrew Thompson 	for (mc = sc->sc_mixer_root; mc;
42163a3f90c6SAndrew Thompson 	    mc = mc->next) {
42173a3f90c6SAndrew Thompson 
42183a3f90c6SAndrew Thompson 		if ((mc->ctl == SOUND_MIXER_NRDEVICES) &&
42193a3f90c6SAndrew Thompson 		    (mc->type == MIX_SELECTOR)) {
42203a3f90c6SAndrew Thompson 
42213a3f90c6SAndrew Thompson 			/* compute selector mask */
42223a3f90c6SAndrew Thompson 
42233a3f90c6SAndrew Thompson 			mask = 0;
42243a3f90c6SAndrew Thompson 			for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) {
42253a3f90c6SAndrew Thompson 				mask |= (1 << mc->slctrtype[i - 1]);
42263a3f90c6SAndrew Thompson 			}
42273a3f90c6SAndrew Thompson 
42283a3f90c6SAndrew Thompson 			temp = mask & src;
42293a3f90c6SAndrew Thompson 			if (temp == 0) {
42303a3f90c6SAndrew Thompson 				continue;
42313a3f90c6SAndrew Thompson 			}
42323a3f90c6SAndrew Thompson 			/* find the first set bit */
42333a3f90c6SAndrew Thompson 			temp = (-temp) & temp;
42343a3f90c6SAndrew Thompson 
42353a3f90c6SAndrew Thompson 			/* update "src" */
42363a3f90c6SAndrew Thompson 			src &= ~mask;
42373a3f90c6SAndrew Thompson 			src |= temp;
42383a3f90c6SAndrew Thompson 
42393a3f90c6SAndrew Thompson 			for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) {
42403a3f90c6SAndrew Thompson 				if (temp != (1 << mc->slctrtype[i - 1])) {
42413a3f90c6SAndrew Thompson 					continue;
42423a3f90c6SAndrew Thompson 				}
42433a3f90c6SAndrew Thompson 				uaudio_mixer_ctl_set(sc, mc, 0, i);
42443a3f90c6SAndrew Thompson 				break;
42453a3f90c6SAndrew Thompson 			}
42463a3f90c6SAndrew Thompson 		}
42473a3f90c6SAndrew Thompson 	}
42483a3f90c6SAndrew Thompson 	return (src);
42493a3f90c6SAndrew Thompson }
42503a3f90c6SAndrew Thompson 
42513a3f90c6SAndrew Thompson /*========================================================================*
42523a3f90c6SAndrew Thompson  * MIDI support routines
42533a3f90c6SAndrew Thompson  *========================================================================*/
42543a3f90c6SAndrew Thompson 
42553a3f90c6SAndrew Thompson static void
4256ed6d949aSAndrew Thompson umidi_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
42573a3f90c6SAndrew Thompson {
4258ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usbd_xfer_softc(xfer);
42593a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub;
4260ed6d949aSAndrew Thompson 	struct usb_page_cache *pc;
42616f068a43SHans Petter Selasky 	uint8_t buf[4];
42623a3f90c6SAndrew Thompson 	uint8_t cmd_len;
42633a3f90c6SAndrew Thompson 	uint8_t cn;
42643a3f90c6SAndrew Thompson 	uint16_t pos;
4265ed6d949aSAndrew Thompson 	int actlen;
4266ed6d949aSAndrew Thompson 
4267ed6d949aSAndrew Thompson 	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
42683a3f90c6SAndrew Thompson 
42693a3f90c6SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
42703a3f90c6SAndrew Thompson 	case USB_ST_TRANSFERRED:
42713a3f90c6SAndrew Thompson 
4272ed6d949aSAndrew Thompson 		DPRINTF("actlen=%d bytes\n", actlen);
42733a3f90c6SAndrew Thompson 
42743a3f90c6SAndrew Thompson 		pos = 0;
4275ed6d949aSAndrew Thompson 		pc = usbd_xfer_get_frame(xfer, 0);
42763a3f90c6SAndrew Thompson 
4277ed6d949aSAndrew Thompson 		while (actlen >= 4) {
42783a3f90c6SAndrew Thompson 
42796f068a43SHans Petter Selasky 			/* copy out the MIDI data */
42806f068a43SHans Petter Selasky 			usbd_copy_out(pc, pos, buf, 4);
42816f068a43SHans Petter Selasky 			/* command length */
42826f068a43SHans Petter Selasky 			cmd_len = umidi_cmd_to_len[buf[0] & 0xF];
42836f068a43SHans Petter Selasky 			/* cable number */
42846f068a43SHans Petter Selasky 			cn = buf[0] >> 4;
42856f068a43SHans Petter Selasky 			/*
42866f068a43SHans Petter Selasky 			 * Lookup sub-channel. The index is range
42876f068a43SHans Petter Selasky 			 * checked below.
42886f068a43SHans Petter Selasky 			 */
42893a3f90c6SAndrew Thompson 			sub = &chan->sub[cn];
42903a3f90c6SAndrew Thompson 
42916f068a43SHans Petter Selasky 			if ((cmd_len != 0) &&
42926f068a43SHans Petter Selasky 			    (cn < chan->max_cable) &&
42936f068a43SHans Petter Selasky 			    (sub->read_open != 0)) {
42943a3f90c6SAndrew Thompson 
42956f068a43SHans Petter Selasky 				/* Send data to the application */
42966f068a43SHans Petter Selasky 				usb_fifo_put_data_linear(
42976f068a43SHans Petter Selasky 				    sub->fifo.fp[USB_FIFO_RX],
42986f068a43SHans Petter Selasky 				    buf + 1, cmd_len, 1);
42996f068a43SHans Petter Selasky 			}
4300ed6d949aSAndrew Thompson 			actlen -= 4;
43013a3f90c6SAndrew Thompson 			pos += 4;
43023a3f90c6SAndrew Thompson 		}
43033a3f90c6SAndrew Thompson 
43043a3f90c6SAndrew Thompson 	case USB_ST_SETUP:
43053a3f90c6SAndrew Thompson 		DPRINTF("start\n");
43066f068a43SHans Petter Selasky tr_setup:
4307ed6d949aSAndrew Thompson 		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
4308a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
43096f068a43SHans Petter Selasky 		break;
43103a3f90c6SAndrew Thompson 
43113a3f90c6SAndrew Thompson 	default:
4312ed6d949aSAndrew Thompson 		DPRINTF("error=%s\n", usbd_errstr(error));
43133a3f90c6SAndrew Thompson 
4314ed6d949aSAndrew Thompson 		if (error != USB_ERR_CANCELLED) {
43153a3f90c6SAndrew Thompson 			/* try to clear stall first */
43166f068a43SHans Petter Selasky 			usbd_xfer_set_stall(xfer);
43176f068a43SHans Petter Selasky 			goto tr_setup;
43183a3f90c6SAndrew Thompson 		}
43196f068a43SHans Petter Selasky 		break;
43203a3f90c6SAndrew Thompson 	}
43213a3f90c6SAndrew Thompson }
43223a3f90c6SAndrew Thompson 
43233a3f90c6SAndrew Thompson /*
43243a3f90c6SAndrew Thompson  * The following statemachine, that converts MIDI commands to
43253a3f90c6SAndrew Thompson  * USB MIDI packets, derives from Linux's usbmidi.c, which
43263a3f90c6SAndrew Thompson  * was written by "Clemens Ladisch":
43273a3f90c6SAndrew Thompson  *
43283a3f90c6SAndrew Thompson  * Returns:
43293a3f90c6SAndrew Thompson  *    0: No command
43303a3f90c6SAndrew Thompson  * Else: Command is complete
43313a3f90c6SAndrew Thompson  */
43323a3f90c6SAndrew Thompson static uint8_t
43333a3f90c6SAndrew Thompson umidi_convert_to_usb(struct umidi_sub_chan *sub, uint8_t cn, uint8_t b)
43343a3f90c6SAndrew Thompson {
43353a3f90c6SAndrew Thompson 	uint8_t p0 = (cn << 4);
43363a3f90c6SAndrew Thompson 
43373a3f90c6SAndrew Thompson 	if (b >= 0xf8) {
43383a3f90c6SAndrew Thompson 		sub->temp_0[0] = p0 | 0x0f;
43393a3f90c6SAndrew Thompson 		sub->temp_0[1] = b;
43403a3f90c6SAndrew Thompson 		sub->temp_0[2] = 0;
43413a3f90c6SAndrew Thompson 		sub->temp_0[3] = 0;
43423a3f90c6SAndrew Thompson 		sub->temp_cmd = sub->temp_0;
43433a3f90c6SAndrew Thompson 		return (1);
43443a3f90c6SAndrew Thompson 
43453a3f90c6SAndrew Thompson 	} else if (b >= 0xf0) {
43463a3f90c6SAndrew Thompson 		switch (b) {
43473a3f90c6SAndrew Thompson 		case 0xf0:		/* system exclusive begin */
43483a3f90c6SAndrew Thompson 			sub->temp_1[1] = b;
43493a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_SYSEX_1;
43503a3f90c6SAndrew Thompson 			break;
43513a3f90c6SAndrew Thompson 		case 0xf1:		/* MIDI time code */
43523a3f90c6SAndrew Thompson 		case 0xf3:		/* song select */
43533a3f90c6SAndrew Thompson 			sub->temp_1[1] = b;
43543a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_1PARAM;
43553a3f90c6SAndrew Thompson 			break;
43563a3f90c6SAndrew Thompson 		case 0xf2:		/* song position pointer */
43573a3f90c6SAndrew Thompson 			sub->temp_1[1] = b;
43583a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_2PARAM_1;
43593a3f90c6SAndrew Thompson 			break;
43603a3f90c6SAndrew Thompson 		case 0xf4:		/* unknown */
43613a3f90c6SAndrew Thompson 		case 0xf5:		/* unknown */
43623a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_UNKNOWN;
43633a3f90c6SAndrew Thompson 			break;
43643a3f90c6SAndrew Thompson 		case 0xf6:		/* tune request */
43653a3f90c6SAndrew Thompson 			sub->temp_1[0] = p0 | 0x05;
43663a3f90c6SAndrew Thompson 			sub->temp_1[1] = 0xf6;
43673a3f90c6SAndrew Thompson 			sub->temp_1[2] = 0;
43683a3f90c6SAndrew Thompson 			sub->temp_1[3] = 0;
43693a3f90c6SAndrew Thompson 			sub->temp_cmd = sub->temp_1;
43703a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_UNKNOWN;
43713a3f90c6SAndrew Thompson 			return (1);
43723a3f90c6SAndrew Thompson 
43733a3f90c6SAndrew Thompson 		case 0xf7:		/* system exclusive end */
43743a3f90c6SAndrew Thompson 			switch (sub->state) {
43753a3f90c6SAndrew Thompson 			case UMIDI_ST_SYSEX_0:
43763a3f90c6SAndrew Thompson 				sub->temp_1[0] = p0 | 0x05;
43773a3f90c6SAndrew Thompson 				sub->temp_1[1] = 0xf7;
43783a3f90c6SAndrew Thompson 				sub->temp_1[2] = 0;
43793a3f90c6SAndrew Thompson 				sub->temp_1[3] = 0;
43803a3f90c6SAndrew Thompson 				sub->temp_cmd = sub->temp_1;
43813a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_UNKNOWN;
43823a3f90c6SAndrew Thompson 				return (1);
43833a3f90c6SAndrew Thompson 			case UMIDI_ST_SYSEX_1:
43843a3f90c6SAndrew Thompson 				sub->temp_1[0] = p0 | 0x06;
43853a3f90c6SAndrew Thompson 				sub->temp_1[2] = 0xf7;
43863a3f90c6SAndrew Thompson 				sub->temp_1[3] = 0;
43873a3f90c6SAndrew Thompson 				sub->temp_cmd = sub->temp_1;
43883a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_UNKNOWN;
43893a3f90c6SAndrew Thompson 				return (1);
43903a3f90c6SAndrew Thompson 			case UMIDI_ST_SYSEX_2:
43913a3f90c6SAndrew Thompson 				sub->temp_1[0] = p0 | 0x07;
43923a3f90c6SAndrew Thompson 				sub->temp_1[3] = 0xf7;
43933a3f90c6SAndrew Thompson 				sub->temp_cmd = sub->temp_1;
43943a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_UNKNOWN;
43953a3f90c6SAndrew Thompson 				return (1);
43963a3f90c6SAndrew Thompson 			}
43973a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_UNKNOWN;
43983a3f90c6SAndrew Thompson 			break;
43993a3f90c6SAndrew Thompson 		}
44003a3f90c6SAndrew Thompson 	} else if (b >= 0x80) {
44013a3f90c6SAndrew Thompson 		sub->temp_1[1] = b;
44023a3f90c6SAndrew Thompson 		if ((b >= 0xc0) && (b <= 0xdf)) {
44033a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_1PARAM;
44043a3f90c6SAndrew Thompson 		} else {
44053a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_2PARAM_1;
44063a3f90c6SAndrew Thompson 		}
44073a3f90c6SAndrew Thompson 	} else {			/* b < 0x80 */
44083a3f90c6SAndrew Thompson 		switch (sub->state) {
44093a3f90c6SAndrew Thompson 		case UMIDI_ST_1PARAM:
44103a3f90c6SAndrew Thompson 			if (sub->temp_1[1] < 0xf0) {
44113a3f90c6SAndrew Thompson 				p0 |= sub->temp_1[1] >> 4;
44123a3f90c6SAndrew Thompson 			} else {
44133a3f90c6SAndrew Thompson 				p0 |= 0x02;
44143a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_UNKNOWN;
44153a3f90c6SAndrew Thompson 			}
44163a3f90c6SAndrew Thompson 			sub->temp_1[0] = p0;
44173a3f90c6SAndrew Thompson 			sub->temp_1[2] = b;
44183a3f90c6SAndrew Thompson 			sub->temp_1[3] = 0;
44193a3f90c6SAndrew Thompson 			sub->temp_cmd = sub->temp_1;
44203a3f90c6SAndrew Thompson 			return (1);
44213a3f90c6SAndrew Thompson 		case UMIDI_ST_2PARAM_1:
44223a3f90c6SAndrew Thompson 			sub->temp_1[2] = b;
44233a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_2PARAM_2;
44243a3f90c6SAndrew Thompson 			break;
44253a3f90c6SAndrew Thompson 		case UMIDI_ST_2PARAM_2:
44263a3f90c6SAndrew Thompson 			if (sub->temp_1[1] < 0xf0) {
44273a3f90c6SAndrew Thompson 				p0 |= sub->temp_1[1] >> 4;
44283a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_2PARAM_1;
44293a3f90c6SAndrew Thompson 			} else {
44303a3f90c6SAndrew Thompson 				p0 |= 0x03;
44313a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_UNKNOWN;
44323a3f90c6SAndrew Thompson 			}
44333a3f90c6SAndrew Thompson 			sub->temp_1[0] = p0;
44343a3f90c6SAndrew Thompson 			sub->temp_1[3] = b;
44353a3f90c6SAndrew Thompson 			sub->temp_cmd = sub->temp_1;
44363a3f90c6SAndrew Thompson 			return (1);
44373a3f90c6SAndrew Thompson 		case UMIDI_ST_SYSEX_0:
44383a3f90c6SAndrew Thompson 			sub->temp_1[1] = b;
44393a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_SYSEX_1;
44403a3f90c6SAndrew Thompson 			break;
44413a3f90c6SAndrew Thompson 		case UMIDI_ST_SYSEX_1:
44423a3f90c6SAndrew Thompson 			sub->temp_1[2] = b;
44433a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_SYSEX_2;
44443a3f90c6SAndrew Thompson 			break;
44453a3f90c6SAndrew Thompson 		case UMIDI_ST_SYSEX_2:
44463a3f90c6SAndrew Thompson 			sub->temp_1[0] = p0 | 0x04;
44473a3f90c6SAndrew Thompson 			sub->temp_1[3] = b;
44483a3f90c6SAndrew Thompson 			sub->temp_cmd = sub->temp_1;
44493a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_SYSEX_0;
44503a3f90c6SAndrew Thompson 			return (1);
44516f068a43SHans Petter Selasky 		default:
44526f068a43SHans Petter Selasky 			break;
44533a3f90c6SAndrew Thompson 		}
44543a3f90c6SAndrew Thompson 	}
44553a3f90c6SAndrew Thompson 	return (0);
44563a3f90c6SAndrew Thompson }
44573a3f90c6SAndrew Thompson 
44583a3f90c6SAndrew Thompson static void
4459ed6d949aSAndrew Thompson umidi_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
44603a3f90c6SAndrew Thompson {
4461ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usbd_xfer_softc(xfer);
44623a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub;
4463ed6d949aSAndrew Thompson 	struct usb_page_cache *pc;
44643a3f90c6SAndrew Thompson 	uint32_t actlen;
4465910f1dcfSHans Petter Selasky 	uint16_t nframes;
44663a3f90c6SAndrew Thompson 	uint8_t buf;
44673a3f90c6SAndrew Thompson 	uint8_t start_cable;
44683a3f90c6SAndrew Thompson 	uint8_t tr_any;
4469ed6d949aSAndrew Thompson 	int len;
4470ed6d949aSAndrew Thompson 
4471ed6d949aSAndrew Thompson 	usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
44723a3f90c6SAndrew Thompson 
4473910f1dcfSHans Petter Selasky 	/*
4474910f1dcfSHans Petter Selasky 	 * NOTE: Some MIDI devices only accept 4 bytes of data per
4475910f1dcfSHans Petter Selasky 	 * short terminated USB transfer.
4476910f1dcfSHans Petter Selasky 	 */
44773a3f90c6SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
44783a3f90c6SAndrew Thompson 	case USB_ST_TRANSFERRED:
4479ed6d949aSAndrew Thompson 		DPRINTF("actlen=%d bytes\n", len);
44803a3f90c6SAndrew Thompson 
44813a3f90c6SAndrew Thompson 	case USB_ST_SETUP:
44826f068a43SHans Petter Selasky tr_setup:
44833a3f90c6SAndrew Thompson 		DPRINTF("start\n");
44843a3f90c6SAndrew Thompson 
4485910f1dcfSHans Petter Selasky 		nframes = 0;	/* reset */
44863a3f90c6SAndrew Thompson 		start_cable = chan->curr_cable;
44873a3f90c6SAndrew Thompson 		tr_any = 0;
44884944c3a8SHans Petter Selasky 		pc = usbd_xfer_get_frame(xfer, 0);
44893a3f90c6SAndrew Thompson 
44903a3f90c6SAndrew Thompson 		while (1) {
44913a3f90c6SAndrew Thompson 
44923a3f90c6SAndrew Thompson 			/* round robin de-queueing */
44933a3f90c6SAndrew Thompson 
44943a3f90c6SAndrew Thompson 			sub = &chan->sub[chan->curr_cable];
44953a3f90c6SAndrew Thompson 
44963a3f90c6SAndrew Thompson 			if (sub->write_open) {
4497910f1dcfSHans Petter Selasky 				usb_fifo_get_data_linear(sub->fifo.fp[USB_FIFO_TX],
4498910f1dcfSHans Petter Selasky 				    &buf, 1, &actlen, 0);
44993a3f90c6SAndrew Thompson 			} else {
45003a3f90c6SAndrew Thompson 				actlen = 0;
45013a3f90c6SAndrew Thompson 			}
45023a3f90c6SAndrew Thompson 
45033a3f90c6SAndrew Thompson 			if (actlen) {
45043a3f90c6SAndrew Thompson 
45053a3f90c6SAndrew Thompson 				tr_any = 1;
45063a3f90c6SAndrew Thompson 
4507910f1dcfSHans Petter Selasky 				DPRINTF("byte=0x%02x from FIFO %u\n", buf,
4508910f1dcfSHans Petter Selasky 				    (unsigned int)chan->curr_cable);
45093a3f90c6SAndrew Thompson 
45103a3f90c6SAndrew Thompson 				if (umidi_convert_to_usb(sub, chan->curr_cable, buf)) {
45113a3f90c6SAndrew Thompson 
4512910f1dcfSHans Petter Selasky 					DPRINTF("sub=0x%02x 0x%02x 0x%02x 0x%02x\n",
45133a3f90c6SAndrew Thompson 					    sub->temp_cmd[0], sub->temp_cmd[1],
45143a3f90c6SAndrew Thompson 					    sub->temp_cmd[2], sub->temp_cmd[3]);
45153a3f90c6SAndrew Thompson 
45164944c3a8SHans Petter Selasky 					usbd_copy_in(pc, nframes * 4, sub->temp_cmd, 4);
4517910f1dcfSHans Petter Selasky 
4518910f1dcfSHans Petter Selasky 					nframes++;
45194944c3a8SHans Petter Selasky 
45204944c3a8SHans Petter Selasky 					if ((nframes >= UMIDI_TX_FRAMES) || (chan->single_command != 0))
45213a3f90c6SAndrew Thompson 						break;
45223a3f90c6SAndrew Thompson 				} else {
45233a3f90c6SAndrew Thompson 					continue;
45243a3f90c6SAndrew Thompson 				}
45253a3f90c6SAndrew Thompson 			}
4526910f1dcfSHans Petter Selasky 
45273a3f90c6SAndrew Thompson 			chan->curr_cable++;
4528910f1dcfSHans Petter Selasky 			if (chan->curr_cable >= chan->max_cable)
45293a3f90c6SAndrew Thompson 				chan->curr_cable = 0;
4530910f1dcfSHans Petter Selasky 
45313a3f90c6SAndrew Thompson 			if (chan->curr_cable == start_cable) {
4532910f1dcfSHans Petter Selasky 				if (tr_any == 0)
45333a3f90c6SAndrew Thompson 					break;
45343a3f90c6SAndrew Thompson 				tr_any = 0;
45353a3f90c6SAndrew Thompson 			}
45363a3f90c6SAndrew Thompson 		}
45373a3f90c6SAndrew Thompson 
45384944c3a8SHans Petter Selasky 		if (nframes != 0) {
4539910f1dcfSHans Petter Selasky 			DPRINTF("Transferring %d frames\n", (int)nframes);
45404944c3a8SHans Petter Selasky 			usbd_xfer_set_frame_len(xfer, 0, 4 * nframes);
4541a593f6b8SAndrew Thompson 			usbd_transfer_submit(xfer);
45423a3f90c6SAndrew Thompson 		}
45436f068a43SHans Petter Selasky 		break;
45443a3f90c6SAndrew Thompson 
45453a3f90c6SAndrew Thompson 	default:			/* Error */
45463a3f90c6SAndrew Thompson 
4547ed6d949aSAndrew Thompson 		DPRINTF("error=%s\n", usbd_errstr(error));
45483a3f90c6SAndrew Thompson 
4549ed6d949aSAndrew Thompson 		if (error != USB_ERR_CANCELLED) {
45503a3f90c6SAndrew Thompson 			/* try to clear stall first */
45516f068a43SHans Petter Selasky 			usbd_xfer_set_stall(xfer);
45526f068a43SHans Petter Selasky 			goto tr_setup;
45533a3f90c6SAndrew Thompson 		}
45546f068a43SHans Petter Selasky 		break;
45553a3f90c6SAndrew Thompson 	}
45563a3f90c6SAndrew Thompson }
45573a3f90c6SAndrew Thompson 
45583a3f90c6SAndrew Thompson static struct umidi_sub_chan *
4559760bc48eSAndrew Thompson umidi_sub_by_fifo(struct usb_fifo *fifo)
45603a3f90c6SAndrew Thompson {
4561ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
45623a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub;
45633a3f90c6SAndrew Thompson 	uint32_t n;
45643a3f90c6SAndrew Thompson 
45653a3f90c6SAndrew Thompson 	for (n = 0; n < UMIDI_CABLES_MAX; n++) {
45663a3f90c6SAndrew Thompson 		sub = &chan->sub[n];
45673a3f90c6SAndrew Thompson 		if ((sub->fifo.fp[USB_FIFO_RX] == fifo) ||
45683a3f90c6SAndrew Thompson 		    (sub->fifo.fp[USB_FIFO_TX] == fifo)) {
45693a3f90c6SAndrew Thompson 			return (sub);
45703a3f90c6SAndrew Thompson 		}
45713a3f90c6SAndrew Thompson 	}
45723a3f90c6SAndrew Thompson 
4573760bc48eSAndrew Thompson 	panic("%s:%d cannot find usb_fifo!\n",
45743a3f90c6SAndrew Thompson 	    __FILE__, __LINE__);
45753a3f90c6SAndrew Thompson 
45763a3f90c6SAndrew Thompson 	return (NULL);
45773a3f90c6SAndrew Thompson }
45783a3f90c6SAndrew Thompson 
45793a3f90c6SAndrew Thompson static void
4580760bc48eSAndrew Thompson umidi_start_read(struct usb_fifo *fifo)
45813a3f90c6SAndrew Thompson {
4582ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
45833a3f90c6SAndrew Thompson 
45846f068a43SHans Petter Selasky 	usbd_transfer_start(chan->xfer[UMIDI_RX_TRANSFER]);
45853a3f90c6SAndrew Thompson }
45863a3f90c6SAndrew Thompson 
45873a3f90c6SAndrew Thompson static void
4588760bc48eSAndrew Thompson umidi_stop_read(struct usb_fifo *fifo)
45893a3f90c6SAndrew Thompson {
4590ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
45913a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo);
45923a3f90c6SAndrew Thompson 
45933a3f90c6SAndrew Thompson 	DPRINTF("\n");
45943a3f90c6SAndrew Thompson 
45953a3f90c6SAndrew Thompson 	sub->read_open = 0;
45963a3f90c6SAndrew Thompson 
45973a3f90c6SAndrew Thompson 	if (--(chan->read_open_refcount) == 0) {
45983a3f90c6SAndrew Thompson 		/*
45993a3f90c6SAndrew Thompson 		 * XXX don't stop the read transfer here, hence that causes
46003a3f90c6SAndrew Thompson 		 * problems with some MIDI adapters
46013a3f90c6SAndrew Thompson 		 */
46023a3f90c6SAndrew Thompson 		DPRINTF("(stopping read transfer)\n");
46033a3f90c6SAndrew Thompson 	}
46043a3f90c6SAndrew Thompson }
46053a3f90c6SAndrew Thompson 
46063a3f90c6SAndrew Thompson static void
4607760bc48eSAndrew Thompson umidi_start_write(struct usb_fifo *fifo)
46083a3f90c6SAndrew Thompson {
4609ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
46103a3f90c6SAndrew Thompson 
46116f068a43SHans Petter Selasky 	usbd_transfer_start(chan->xfer[UMIDI_TX_TRANSFER]);
46123a3f90c6SAndrew Thompson }
46133a3f90c6SAndrew Thompson 
46143a3f90c6SAndrew Thompson static void
4615760bc48eSAndrew Thompson umidi_stop_write(struct usb_fifo *fifo)
46163a3f90c6SAndrew Thompson {
4617ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
46183a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo);
46193a3f90c6SAndrew Thompson 
46203a3f90c6SAndrew Thompson 	DPRINTF("\n");
46213a3f90c6SAndrew Thompson 
46223a3f90c6SAndrew Thompson 	sub->write_open = 0;
46233a3f90c6SAndrew Thompson 
46243a3f90c6SAndrew Thompson 	if (--(chan->write_open_refcount) == 0) {
46253a3f90c6SAndrew Thompson 		DPRINTF("(stopping write transfer)\n");
46266f068a43SHans Petter Selasky 		usbd_transfer_stop(chan->xfer[UMIDI_TX_TRANSFER]);
46273a3f90c6SAndrew Thompson 	}
46283a3f90c6SAndrew Thompson }
46293a3f90c6SAndrew Thompson 
46303a3f90c6SAndrew Thompson static int
4631760bc48eSAndrew Thompson umidi_open(struct usb_fifo *fifo, int fflags)
46323a3f90c6SAndrew Thompson {
4633ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
46343a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo);
46353a3f90c6SAndrew Thompson 
46363a3f90c6SAndrew Thompson 	if (fflags & FREAD) {
4637a593f6b8SAndrew Thompson 		if (usb_fifo_alloc_buffer(fifo, 4, (1024 / 4))) {
46383a3f90c6SAndrew Thompson 			return (ENOMEM);
46393a3f90c6SAndrew Thompson 		}
46407e6e6b67SAndrew Thompson 		mtx_lock(&chan->mtx);
46413a3f90c6SAndrew Thompson 		chan->read_open_refcount++;
46423a3f90c6SAndrew Thompson 		sub->read_open = 1;
46437e6e6b67SAndrew Thompson 		mtx_unlock(&chan->mtx);
46443a3f90c6SAndrew Thompson 	}
46453a3f90c6SAndrew Thompson 	if (fflags & FWRITE) {
4646a593f6b8SAndrew Thompson 		if (usb_fifo_alloc_buffer(fifo, 32, (1024 / 32))) {
46473a3f90c6SAndrew Thompson 			return (ENOMEM);
46483a3f90c6SAndrew Thompson 		}
46493a3f90c6SAndrew Thompson 		/* clear stall first */
46507e6e6b67SAndrew Thompson 		mtx_lock(&chan->mtx);
46516f068a43SHans Petter Selasky 		usbd_xfer_set_stall(chan->xfer[UMIDI_TX_TRANSFER]);
46523a3f90c6SAndrew Thompson 		chan->write_open_refcount++;
46533a3f90c6SAndrew Thompson 		sub->write_open = 1;
46543a3f90c6SAndrew Thompson 
46553a3f90c6SAndrew Thompson 		/* reset */
46563a3f90c6SAndrew Thompson 		sub->state = UMIDI_ST_UNKNOWN;
46577e6e6b67SAndrew Thompson 		mtx_unlock(&chan->mtx);
46583a3f90c6SAndrew Thompson 	}
46593a3f90c6SAndrew Thompson 	return (0);			/* success */
46603a3f90c6SAndrew Thompson }
46613a3f90c6SAndrew Thompson 
46623a3f90c6SAndrew Thompson static void
4663760bc48eSAndrew Thompson umidi_close(struct usb_fifo *fifo, int fflags)
46643a3f90c6SAndrew Thompson {
46653a3f90c6SAndrew Thompson 	if (fflags & FREAD) {
4666a593f6b8SAndrew Thompson 		usb_fifo_free_buffer(fifo);
46673a3f90c6SAndrew Thompson 	}
46683a3f90c6SAndrew Thompson 	if (fflags & FWRITE) {
4669a593f6b8SAndrew Thompson 		usb_fifo_free_buffer(fifo);
46703a3f90c6SAndrew Thompson 	}
46713a3f90c6SAndrew Thompson }
46723a3f90c6SAndrew Thompson 
46733a3f90c6SAndrew Thompson 
46743a3f90c6SAndrew Thompson static int
4675760bc48eSAndrew Thompson umidi_ioctl(struct usb_fifo *fifo, u_long cmd, void *data,
4676ee3e3ff5SAndrew Thompson     int fflags)
46773a3f90c6SAndrew Thompson {
46783a3f90c6SAndrew Thompson 	return (ENODEV);
46793a3f90c6SAndrew Thompson }
46803a3f90c6SAndrew Thompson 
46813a3f90c6SAndrew Thompson static void
46823a3f90c6SAndrew Thompson umidi_init(device_t dev)
46833a3f90c6SAndrew Thompson {
46843a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(dev);
46853a3f90c6SAndrew Thompson 	struct umidi_chan *chan = &sc->sc_midi_chan;
46863a3f90c6SAndrew Thompson 
46873a3f90c6SAndrew Thompson 	mtx_init(&chan->mtx, "umidi lock", NULL, MTX_DEF | MTX_RECURSE);
46883a3f90c6SAndrew Thompson }
46893a3f90c6SAndrew Thompson 
4690760bc48eSAndrew Thompson static struct usb_fifo_methods umidi_fifo_methods = {
46913a3f90c6SAndrew Thompson 	.f_start_read = &umidi_start_read,
46923a3f90c6SAndrew Thompson 	.f_start_write = &umidi_start_write,
46933a3f90c6SAndrew Thompson 	.f_stop_read = &umidi_stop_read,
46943a3f90c6SAndrew Thompson 	.f_stop_write = &umidi_stop_write,
46953a3f90c6SAndrew Thompson 	.f_open = &umidi_open,
46963a3f90c6SAndrew Thompson 	.f_close = &umidi_close,
46973a3f90c6SAndrew Thompson 	.f_ioctl = &umidi_ioctl,
46983a3f90c6SAndrew Thompson 	.basename[0] = "umidi",
46993a3f90c6SAndrew Thompson };
47003a3f90c6SAndrew Thompson 
470125b74dabSHans Petter Selasky static int
47023a3f90c6SAndrew Thompson umidi_probe(device_t dev)
47033a3f90c6SAndrew Thompson {
47043a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(dev);
4705760bc48eSAndrew Thompson 	struct usb_attach_arg *uaa = device_get_ivars(dev);
47063a3f90c6SAndrew Thompson 	struct umidi_chan *chan = &sc->sc_midi_chan;
47073a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub;
47083a3f90c6SAndrew Thompson 	int unit = device_get_unit(dev);
47093a3f90c6SAndrew Thompson 	int error;
47103a3f90c6SAndrew Thompson 	uint32_t n;
47113a3f90c6SAndrew Thompson 
47124944c3a8SHans Petter Selasky 	if (usb_test_quirk(uaa, UQ_SINGLE_CMD_MIDI))
47134944c3a8SHans Petter Selasky 		chan->single_command = 1;
47144944c3a8SHans Petter Selasky 
4715a593f6b8SAndrew Thompson 	if (usbd_set_alt_interface_index(sc->sc_udev, chan->iface_index,
47163a3f90c6SAndrew Thompson 	    chan->iface_alt_index)) {
47173a3f90c6SAndrew Thompson 		DPRINTF("setting of alternate index failed!\n");
47183a3f90c6SAndrew Thompson 		goto detach;
47193a3f90c6SAndrew Thompson 	}
47206f068a43SHans Petter Selasky 	usbd_set_parent_iface(sc->sc_udev, chan->iface_index,
47216f068a43SHans Petter Selasky 	    sc->sc_mixer_iface_index);
47223a3f90c6SAndrew Thompson 
4723a593f6b8SAndrew Thompson 	error = usbd_transfer_setup(uaa->device, &chan->iface_index,
47243a3f90c6SAndrew Thompson 	    chan->xfer, umidi_config, UMIDI_N_TRANSFER,
47253a3f90c6SAndrew Thompson 	    chan, &chan->mtx);
47263a3f90c6SAndrew Thompson 	if (error) {
4727a593f6b8SAndrew Thompson 		DPRINTF("error=%s\n", usbd_errstr(error));
47283a3f90c6SAndrew Thompson 		goto detach;
47293a3f90c6SAndrew Thompson 	}
47303a3f90c6SAndrew Thompson 	if ((chan->max_cable > UMIDI_CABLES_MAX) ||
47313a3f90c6SAndrew Thompson 	    (chan->max_cable == 0)) {
47323a3f90c6SAndrew Thompson 		chan->max_cable = UMIDI_CABLES_MAX;
47333a3f90c6SAndrew Thompson 	}
47343a3f90c6SAndrew Thompson 
47353a3f90c6SAndrew Thompson 	for (n = 0; n < chan->max_cable; n++) {
47363a3f90c6SAndrew Thompson 
47373a3f90c6SAndrew Thompson 		sub = &chan->sub[n];
47383a3f90c6SAndrew Thompson 
4739a593f6b8SAndrew Thompson 		error = usb_fifo_attach(sc->sc_udev, chan, &chan->mtx,
47403a3f90c6SAndrew Thompson 		    &umidi_fifo_methods, &sub->fifo, unit, n,
4741ee3e3ff5SAndrew Thompson 		    chan->iface_index,
4742ee3e3ff5SAndrew Thompson 		    UID_ROOT, GID_OPERATOR, 0644);
47433a3f90c6SAndrew Thompson 		if (error) {
47443a3f90c6SAndrew Thompson 			goto detach;
47453a3f90c6SAndrew Thompson 		}
47463a3f90c6SAndrew Thompson 	}
47473a3f90c6SAndrew Thompson 
47483a3f90c6SAndrew Thompson 	mtx_lock(&chan->mtx);
47493a3f90c6SAndrew Thompson 
47503a3f90c6SAndrew Thompson 	/* clear stall first */
47516f068a43SHans Petter Selasky 	usbd_xfer_set_stall(chan->xfer[UMIDI_RX_TRANSFER]);
47523a3f90c6SAndrew Thompson 
47533a3f90c6SAndrew Thompson 	/*
47546f068a43SHans Petter Selasky 	 * NOTE: At least one device will not work properly unless the
47556f068a43SHans Petter Selasky 	 * BULK IN pipe is open all the time. This might have to do
47566f068a43SHans Petter Selasky 	 * about that the internal queues of the device overflow if we
47576f068a43SHans Petter Selasky 	 * don't read them regularly.
47583a3f90c6SAndrew Thompson 	 */
47596f068a43SHans Petter Selasky 	usbd_transfer_start(chan->xfer[UMIDI_RX_TRANSFER]);
47603a3f90c6SAndrew Thompson 
47613a3f90c6SAndrew Thompson 	mtx_unlock(&chan->mtx);
47623a3f90c6SAndrew Thompson 
47633a3f90c6SAndrew Thompson 	return (0);			/* success */
47643a3f90c6SAndrew Thompson 
47653a3f90c6SAndrew Thompson detach:
47663a3f90c6SAndrew Thompson 	return (ENXIO);			/* failure */
47673a3f90c6SAndrew Thompson }
47683a3f90c6SAndrew Thompson 
476925b74dabSHans Petter Selasky static int
47703a3f90c6SAndrew Thompson umidi_detach(device_t dev)
47713a3f90c6SAndrew Thompson {
47723a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(dev);
47733a3f90c6SAndrew Thompson 	struct umidi_chan *chan = &sc->sc_midi_chan;
47743a3f90c6SAndrew Thompson 	uint32_t n;
47753a3f90c6SAndrew Thompson 
47763a3f90c6SAndrew Thompson 	for (n = 0; n < UMIDI_CABLES_MAX; n++) {
4777a593f6b8SAndrew Thompson 		usb_fifo_detach(&chan->sub[n].fifo);
47783a3f90c6SAndrew Thompson 	}
47793a3f90c6SAndrew Thompson 
47803a3f90c6SAndrew Thompson 	mtx_lock(&chan->mtx);
47813a3f90c6SAndrew Thompson 
47826f068a43SHans Petter Selasky 	usbd_transfer_stop(chan->xfer[UMIDI_RX_TRANSFER]);
47833a3f90c6SAndrew Thompson 
47843a3f90c6SAndrew Thompson 	mtx_unlock(&chan->mtx);
47853a3f90c6SAndrew Thompson 
4786a593f6b8SAndrew Thompson 	usbd_transfer_unsetup(chan->xfer, UMIDI_N_TRANSFER);
47873a3f90c6SAndrew Thompson 
47883a3f90c6SAndrew Thompson 	mtx_destroy(&chan->mtx);
47893a3f90c6SAndrew Thompson 
47903a3f90c6SAndrew Thompson 	return (0);
47913a3f90c6SAndrew Thompson }
47923a3f90c6SAndrew Thompson 
47939aef556dSAndrew Thompson DRIVER_MODULE(uaudio, uhub, uaudio_driver, uaudio_devclass, NULL, 0);
47943a3f90c6SAndrew Thompson MODULE_DEPEND(uaudio, usb, 1, 1, 1);
47953a3f90c6SAndrew Thompson MODULE_DEPEND(uaudio, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
47963a3f90c6SAndrew Thompson MODULE_VERSION(uaudio, 1);
4797