xref: /freebsd/sys/dev/sound/usb/uaudio.c (revision af26e3df)
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>
7476b71212SHans Petter Selasky #include <dev/usb/usbhid.h>
75902514f6SHans Petter Selasky #include <dev/usb/usb_request.h>
76455a367fSHans Petter Selasky #include <dev/usb/usb_process.h>
773a3f90c6SAndrew Thompson 
783a3f90c6SAndrew Thompson #define	USB_DEBUG_VAR uaudio_debug
793a3f90c6SAndrew Thompson #include <dev/usb/usb_debug.h>
803a3f90c6SAndrew Thompson 
813a3f90c6SAndrew Thompson #include <dev/usb/quirk/usb_quirk.h>
823a3f90c6SAndrew Thompson 
833a3f90c6SAndrew Thompson #include <sys/reboot.h>			/* for bootverbose */
843a3f90c6SAndrew Thompson 
8590da2b28SAriff Abdullah #ifdef HAVE_KERNEL_OPTION_HEADERS
8690da2b28SAriff Abdullah #include "opt_snd.h"
8790da2b28SAriff Abdullah #endif
8890da2b28SAriff Abdullah 
893a3f90c6SAndrew Thompson #include <dev/sound/pcm/sound.h>
903a3f90c6SAndrew Thompson #include <dev/sound/usb/uaudioreg.h>
913a3f90c6SAndrew Thompson #include <dev/sound/usb/uaudio.h>
923a3f90c6SAndrew Thompson #include <dev/sound/chip.h>
933a3f90c6SAndrew Thompson #include "feeder_if.h"
943a3f90c6SAndrew Thompson 
95afbfddd9SAndrew Thompson static int uaudio_default_rate = 0;		/* use rate list */
963a3f90c6SAndrew Thompson static int uaudio_default_bits = 32;
97afbfddd9SAndrew Thompson static int uaudio_default_channels = 0;		/* use default */
983a3f90c6SAndrew Thompson 
99b850ecc1SAndrew Thompson #ifdef USB_DEBUG
1003a3f90c6SAndrew Thompson static int uaudio_debug = 0;
1013a3f90c6SAndrew Thompson 
1026472ac3dSEd Schouten static SYSCTL_NODE(_hw_usb, OID_AUTO, uaudio, CTLFLAG_RW, 0, "USB uaudio");
103afbfddd9SAndrew Thompson 
104fadc970bSAndrew Thompson SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, debug, CTLFLAG_RW,
1053a3f90c6SAndrew Thompson     &uaudio_debug, 0, "uaudio debug level");
106afbfddd9SAndrew Thompson 
107afbfddd9SAndrew Thompson TUNABLE_INT("hw.usb.uaudio.default_rate", &uaudio_default_rate);
108fadc970bSAndrew Thompson SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_rate, CTLFLAG_RW,
1093a3f90c6SAndrew Thompson     &uaudio_default_rate, 0, "uaudio default sample rate");
110afbfddd9SAndrew Thompson 
111afbfddd9SAndrew Thompson TUNABLE_INT("hw.usb.uaudio.default_bits", &uaudio_default_bits);
112fadc970bSAndrew Thompson SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_bits, CTLFLAG_RW,
1133a3f90c6SAndrew Thompson     &uaudio_default_bits, 0, "uaudio default sample bits");
114afbfddd9SAndrew Thompson 
115afbfddd9SAndrew Thompson TUNABLE_INT("hw.usb.uaudio.default_channels", &uaudio_default_channels);
116fadc970bSAndrew Thompson SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_channels, CTLFLAG_RW,
1173a3f90c6SAndrew Thompson     &uaudio_default_channels, 0, "uaudio default sample channels");
1183a3f90c6SAndrew Thompson #endif
1193a3f90c6SAndrew Thompson 
120b029f6bbSAndrew Thompson #define	UAUDIO_NFRAMES		64	/* must be factor of 8 due HS-USB */
1213a3f90c6SAndrew Thompson #define	UAUDIO_NCHANBUFS	2	/* number of outstanding request */
122e2524b2eSHans Petter Selasky #define	UAUDIO_RECURSE_LIMIT	255	/* rounds */
1233a3f90c6SAndrew Thompson 
1243a3f90c6SAndrew Thompson #define	MAKE_WORD(h,l) (((h) << 8) | (l))
1253a3f90c6SAndrew Thompson #define	BIT_TEST(bm,bno) (((bm)[(bno) / 8] >> (7 - ((bno) % 8))) & 1)
126c737a632SAndrew Thompson #define	UAUDIO_MAX_CHAN(x) (x)
127f7e62ad0SHans Petter Selasky #define	MIX(sc) ((sc)->sc_mixer_node)
1283a3f90c6SAndrew Thompson 
129e2524b2eSHans Petter Selasky union uaudio_asid {
130e2524b2eSHans Petter Selasky 	const struct usb_audio_streaming_interface_descriptor *v1;
131e2524b2eSHans Petter Selasky 	const struct usb_audio20_streaming_interface_descriptor *v2;
132e2524b2eSHans Petter Selasky };
133e2524b2eSHans Petter Selasky 
134e2524b2eSHans Petter Selasky union uaudio_asf1d {
135e2524b2eSHans Petter Selasky 	const struct usb_audio_streaming_type1_descriptor *v1;
136e2524b2eSHans Petter Selasky 	const struct usb_audio20_streaming_type1_descriptor *v2;
137e2524b2eSHans Petter Selasky };
138e2524b2eSHans Petter Selasky 
139e2524b2eSHans Petter Selasky union uaudio_sed {
140e2524b2eSHans Petter Selasky 	const struct usb_audio_streaming_endpoint_descriptor *v1;
141e2524b2eSHans Petter Selasky 	const struct usb_audio20_streaming_endpoint_descriptor *v2;
142e2524b2eSHans Petter Selasky };
143e2524b2eSHans Petter Selasky 
1443a3f90c6SAndrew Thompson struct uaudio_mixer_node {
145902514f6SHans Petter Selasky 	const char *name;
146902514f6SHans Petter Selasky 
1473a3f90c6SAndrew Thompson 	int32_t	minval;
1483a3f90c6SAndrew Thompson 	int32_t	maxval;
149902514f6SHans Petter Selasky #define	MIX_MAX_CHAN 16
1503a3f90c6SAndrew Thompson 	int32_t	wValue[MIX_MAX_CHAN];	/* using nchan */
1513a3f90c6SAndrew Thompson 	uint32_t mul;
1523a3f90c6SAndrew Thompson 	uint32_t ctl;
1533a3f90c6SAndrew Thompson 
154902514f6SHans Petter Selasky 	int wData[MIX_MAX_CHAN];	/* using nchan */
1553a3f90c6SAndrew Thompson 	uint16_t wIndex;
1563a3f90c6SAndrew Thompson 
1573a3f90c6SAndrew Thompson 	uint8_t	update[(MIX_MAX_CHAN + 7) / 8];
1583a3f90c6SAndrew Thompson 	uint8_t	nchan;
1593a3f90c6SAndrew Thompson 	uint8_t	type;
1603a3f90c6SAndrew Thompson #define	MIX_ON_OFF	1
1613a3f90c6SAndrew Thompson #define	MIX_SIGNED_16	2
1623a3f90c6SAndrew Thompson #define	MIX_UNSIGNED_16	3
1633a3f90c6SAndrew Thompson #define	MIX_SIGNED_8	4
1643a3f90c6SAndrew Thompson #define	MIX_SELECTOR	5
1653a3f90c6SAndrew Thompson #define	MIX_UNKNOWN     6
1663a3f90c6SAndrew Thompson #define	MIX_SIZE(n) ((((n) == MIX_SIGNED_16) || \
1673a3f90c6SAndrew Thompson 		      ((n) == MIX_UNSIGNED_16)) ? 2 : 1)
1683a3f90c6SAndrew Thompson #define	MIX_UNSIGNED(n) ((n) == MIX_UNSIGNED_16)
1693a3f90c6SAndrew Thompson 
1703a3f90c6SAndrew Thompson #define	MAX_SELECTOR_INPUT_PIN 256
1713a3f90c6SAndrew Thompson 	uint8_t	slctrtype[MAX_SELECTOR_INPUT_PIN];
1723a3f90c6SAndrew Thompson 	uint8_t	class;
173ff4d5953SHans Petter Selasky 	uint8_t val_default;
1743a3f90c6SAndrew Thompson 
175902514f6SHans Petter Selasky 	uint8_t desc[64];
176902514f6SHans Petter Selasky 
1773a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *next;
1783a3f90c6SAndrew Thompson };
1793a3f90c6SAndrew Thompson 
180455a367fSHans Petter Selasky struct uaudio_configure_msg {
181455a367fSHans Petter Selasky 	struct usb_proc_msg hdr;
182455a367fSHans Petter Selasky 	struct uaudio_softc *sc;
183455a367fSHans Petter Selasky };
1843a3f90c6SAndrew Thompson 
1852c2752d3SHans Petter Selasky #define	CHAN_MAX_ALT 24
186455a367fSHans Petter Selasky 
187455a367fSHans Petter Selasky struct uaudio_chan_alt {
188e2524b2eSHans Petter Selasky 	union uaudio_asf1d p_asf1d;
189e2524b2eSHans Petter Selasky 	union uaudio_sed p_sed;
1904c21be9bSRebecca Cran 	const usb_endpoint_descriptor_audio_t *p_ed1;
1913a3f90c6SAndrew Thompson 	const struct uaudio_format *p_fmt;
192455a367fSHans Petter Selasky 	const struct usb_config *usb_cfg;
193455a367fSHans Petter Selasky 	uint32_t sample_rate;	/* in Hz */
194455a367fSHans Petter Selasky 	uint16_t sample_size;
195455a367fSHans Petter Selasky 	uint8_t	iface_index;
196455a367fSHans Petter Selasky 	uint8_t	iface_alt_index;
197455a367fSHans Petter Selasky 	uint8_t channels;
198455a367fSHans Petter Selasky };
199455a367fSHans Petter Selasky 
200455a367fSHans Petter Selasky struct uaudio_chan {
201455a367fSHans Petter Selasky 	struct pcmchan_caps pcm_cap;	/* capabilities */
202455a367fSHans Petter Selasky 	struct uaudio_chan_alt usb_alt[CHAN_MAX_ALT];
203455a367fSHans Petter Selasky 	struct snd_dbuf *pcm_buf;
204455a367fSHans Petter Selasky 	struct mtx *pcm_mtx;		/* lock protecting this structure */
205455a367fSHans Petter Selasky 	struct uaudio_softc *priv_sc;
206455a367fSHans Petter Selasky 	struct pcm_channel *pcm_ch;
207455a367fSHans Petter Selasky 	struct usb_xfer *xfer[UAUDIO_NCHANBUFS + 1];
2083a3f90c6SAndrew Thompson 
2093a3f90c6SAndrew Thompson 	uint8_t *buf;			/* pointer to buffer */
2103a3f90c6SAndrew Thompson 	uint8_t *start;			/* upper layer buffer start */
2113a3f90c6SAndrew Thompson 	uint8_t *end;			/* upper layer buffer end */
2123a3f90c6SAndrew Thompson 	uint8_t *cur;			/* current position in upper layer
2133a3f90c6SAndrew Thompson 					 * buffer */
2143a3f90c6SAndrew Thompson 
215b029f6bbSAndrew Thompson 	uint32_t intr_frames;		/* in units */
216afbfddd9SAndrew Thompson 	uint32_t frames_per_second;
217afbfddd9SAndrew Thompson 	uint32_t sample_rem;
218afbfddd9SAndrew Thompson 	uint32_t sample_curr;
219455a367fSHans Petter Selasky 	uint32_t max_buf;
220afbfddd9SAndrew Thompson 
2213a3f90c6SAndrew Thompson 	uint32_t pcm_format[2];
2223a3f90c6SAndrew Thompson 
223afbfddd9SAndrew Thompson 	uint16_t bytes_per_frame[2];
224afbfddd9SAndrew Thompson 
225455a367fSHans Petter Selasky 	uint8_t	num_alt;
226455a367fSHans Petter Selasky 	uint8_t cur_alt;
227455a367fSHans Petter Selasky 	uint8_t set_alt;
228455a367fSHans Petter Selasky 	uint8_t operation;
229455a367fSHans Petter Selasky #define	CHAN_OP_NONE 0
230455a367fSHans Petter Selasky #define	CHAN_OP_START 1
231455a367fSHans Petter Selasky #define	CHAN_OP_STOP 2
232455a367fSHans Petter Selasky #define	CHAN_OP_DRAIN 3
233b4380da7SHans Petter Selasky 
234b4380da7SHans Petter Selasky 	uint8_t last_sync_time;
235b4380da7SHans Petter Selasky 	uint8_t last_sync_state;
236b4380da7SHans Petter Selasky #define	UAUDIO_SYNC_NONE 0
237b4380da7SHans Petter Selasky #define	UAUDIO_SYNC_MORE 1
238b4380da7SHans Petter Selasky #define	UAUDIO_SYNC_LESS 2
2393a3f90c6SAndrew Thompson };
2403a3f90c6SAndrew Thompson 
2413a3f90c6SAndrew Thompson #define	UMIDI_CABLES_MAX   16		/* units */
242e0b17a62SHans Petter Selasky #define	UMIDI_TX_FRAMES	   256		/* units */
243910f1dcfSHans Petter Selasky #define	UMIDI_TX_BUFFER    (UMIDI_TX_FRAMES * 4)	/* bytes */
2443a3f90c6SAndrew Thompson 
2456f068a43SHans Petter Selasky enum {
2466f068a43SHans Petter Selasky 	UMIDI_TX_TRANSFER,
2476f068a43SHans Petter Selasky 	UMIDI_RX_TRANSFER,
2486f068a43SHans Petter Selasky 	UMIDI_N_TRANSFER,
2496f068a43SHans Petter Selasky };
2506f068a43SHans Petter Selasky 
2513a3f90c6SAndrew Thompson struct umidi_sub_chan {
252760bc48eSAndrew Thompson 	struct usb_fifo_sc fifo;
2533a3f90c6SAndrew Thompson 	uint8_t *temp_cmd;
2543a3f90c6SAndrew Thompson 	uint8_t	temp_0[4];
2553a3f90c6SAndrew Thompson 	uint8_t	temp_1[4];
2563a3f90c6SAndrew Thompson 	uint8_t	state;
2573a3f90c6SAndrew Thompson #define	UMIDI_ST_UNKNOWN   0		/* scan for command */
2583a3f90c6SAndrew Thompson #define	UMIDI_ST_1PARAM    1
2593a3f90c6SAndrew Thompson #define	UMIDI_ST_2PARAM_1  2
2603a3f90c6SAndrew Thompson #define	UMIDI_ST_2PARAM_2  3
2613a3f90c6SAndrew Thompson #define	UMIDI_ST_SYSEX_0   4
2623a3f90c6SAndrew Thompson #define	UMIDI_ST_SYSEX_1   5
2633a3f90c6SAndrew Thompson #define	UMIDI_ST_SYSEX_2   6
2643a3f90c6SAndrew Thompson 
2653a3f90c6SAndrew Thompson 	uint8_t	read_open:1;
2663a3f90c6SAndrew Thompson 	uint8_t	write_open:1;
2673a3f90c6SAndrew Thompson 	uint8_t	unused:6;
2683a3f90c6SAndrew Thompson };
2693a3f90c6SAndrew Thompson 
2703a3f90c6SAndrew Thompson struct umidi_chan {
2713a3f90c6SAndrew Thompson 
2723a3f90c6SAndrew Thompson 	struct umidi_sub_chan sub[UMIDI_CABLES_MAX];
2733a3f90c6SAndrew Thompson 	struct mtx mtx;
2743a3f90c6SAndrew Thompson 
275760bc48eSAndrew Thompson 	struct usb_xfer *xfer[UMIDI_N_TRANSFER];
2763a3f90c6SAndrew Thompson 
2773a3f90c6SAndrew Thompson 	uint8_t	iface_index;
2783a3f90c6SAndrew Thompson 	uint8_t	iface_alt_index;
2793a3f90c6SAndrew Thompson 
2803a3f90c6SAndrew Thompson 	uint8_t	read_open_refcount;
2813a3f90c6SAndrew Thompson 	uint8_t	write_open_refcount;
2823a3f90c6SAndrew Thompson 
2833a3f90c6SAndrew Thompson 	uint8_t	curr_cable;
2843a3f90c6SAndrew Thompson 	uint8_t	max_cable;
2853a3f90c6SAndrew Thompson 	uint8_t	valid;
2864944c3a8SHans Petter Selasky 	uint8_t single_command;
2873a3f90c6SAndrew Thompson };
2883a3f90c6SAndrew Thompson 
289e2524b2eSHans Petter Selasky struct uaudio_search_result {
290e2524b2eSHans Petter Selasky 	uint8_t	bit_input[(256 + 7) / 8];
291e2524b2eSHans Petter Selasky 	uint8_t	bit_output[(256 + 7) / 8];
292e2524b2eSHans Petter Selasky 	uint8_t	recurse_level;
293e2524b2eSHans Petter Selasky 	uint8_t	id_max;
294e2524b2eSHans Petter Selasky 	uint8_t is_input;
295e2524b2eSHans Petter Selasky };
296e2524b2eSHans Petter Selasky 
29776b71212SHans Petter Selasky enum {
29876b71212SHans Petter Selasky 	UAUDIO_HID_RX_TRANSFER,
29976b71212SHans Petter Selasky 	UAUDIO_HID_N_TRANSFER,
30076b71212SHans Petter Selasky };
30176b71212SHans Petter Selasky 
30276b71212SHans Petter Selasky struct uaudio_hid {
30376b71212SHans Petter Selasky 	struct usb_xfer *xfer[UAUDIO_HID_N_TRANSFER];
30476b71212SHans Petter Selasky 	struct hid_location volume_up_loc;
30576b71212SHans Petter Selasky 	struct hid_location volume_down_loc;
3062ba0f361SHans Petter Selasky 	struct hid_location mute_loc;
30776b71212SHans Petter Selasky 	uint32_t flags;
30876b71212SHans Petter Selasky #define	UAUDIO_HID_VALID		0x0001
30976b71212SHans Petter Selasky #define	UAUDIO_HID_HAS_ID		0x0002
31076b71212SHans Petter Selasky #define	UAUDIO_HID_HAS_VOLUME_UP	0x0004
31176b71212SHans Petter Selasky #define	UAUDIO_HID_HAS_VOLUME_DOWN	0x0008
3122ba0f361SHans Petter Selasky #define	UAUDIO_HID_HAS_MUTE		0x0010
31376b71212SHans Petter Selasky 	uint8_t iface_index;
31476b71212SHans Petter Selasky 	uint8_t volume_up_id;
31576b71212SHans Petter Selasky 	uint8_t volume_down_id;
3162ba0f361SHans Petter Selasky 	uint8_t mute_id;
31776b71212SHans Petter Selasky };
31876b71212SHans Petter Selasky 
3193a3f90c6SAndrew Thompson struct uaudio_softc {
3203a3f90c6SAndrew Thompson 	struct sbuf sc_sndstat;
3213a3f90c6SAndrew Thompson 	struct sndcard_func sc_sndcard_func;
3223a3f90c6SAndrew Thompson 	struct uaudio_chan sc_rec_chan;
3233a3f90c6SAndrew Thompson 	struct uaudio_chan sc_play_chan;
3243a3f90c6SAndrew Thompson 	struct umidi_chan sc_midi_chan;
32576b71212SHans Petter Selasky 	struct uaudio_hid sc_hid;
326e2524b2eSHans Petter Selasky 	struct uaudio_search_result sc_mixer_clocks;
327f7e62ad0SHans Petter Selasky 	struct uaudio_mixer_node sc_mixer_node;
328455a367fSHans Petter Selasky 	struct uaudio_configure_msg sc_config_msg[2];
3293a3f90c6SAndrew Thompson 
330902514f6SHans Petter Selasky 	struct mtx *sc_mixer_lock;
33176b71212SHans Petter Selasky 	struct snd_mixer *sc_mixer_dev;
332760bc48eSAndrew Thompson 	struct usb_device *sc_udev;
333760bc48eSAndrew Thompson 	struct usb_xfer *sc_mixer_xfer[1];
3343a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *sc_mixer_root;
3353a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *sc_mixer_curr;
3363a3f90c6SAndrew Thompson 
3373a3f90c6SAndrew Thompson 	uint32_t sc_mix_info;
3383a3f90c6SAndrew Thompson 	uint32_t sc_recsrc_info;
3393a3f90c6SAndrew Thompson 
3403a3f90c6SAndrew Thompson 	uint16_t sc_audio_rev;
3413a3f90c6SAndrew Thompson 	uint16_t sc_mixer_count;
3423a3f90c6SAndrew Thompson 
3433a3f90c6SAndrew Thompson 	uint8_t	sc_sndstat_valid;
3443a3f90c6SAndrew Thompson 	uint8_t	sc_mixer_iface_index;
3453a3f90c6SAndrew Thompson 	uint8_t	sc_mixer_iface_no;
3463a3f90c6SAndrew Thompson 	uint8_t	sc_mixer_chan;
3473a3f90c6SAndrew Thompson 	uint8_t	sc_pcm_registered:1;
3483a3f90c6SAndrew Thompson 	uint8_t	sc_mixer_init:1;
3493a3f90c6SAndrew Thompson 	uint8_t	sc_uq_audio_swap_lr:1;
3503a3f90c6SAndrew Thompson 	uint8_t	sc_uq_au_inp_async:1;
3513a3f90c6SAndrew Thompson 	uint8_t	sc_uq_au_no_xu:1;
3523a3f90c6SAndrew Thompson 	uint8_t	sc_uq_bad_adc:1;
35325b74dabSHans Petter Selasky 	uint8_t	sc_uq_au_vendor_class:1;
3543a3f90c6SAndrew Thompson };
3553a3f90c6SAndrew Thompson 
3563a3f90c6SAndrew Thompson struct uaudio_terminal_node {
3573a3f90c6SAndrew Thompson 	union {
358760bc48eSAndrew Thompson 		const struct usb_descriptor *desc;
359e2524b2eSHans Petter Selasky 		const struct usb_audio_input_terminal *it_v1;
360e2524b2eSHans Petter Selasky 		const struct usb_audio_output_terminal *ot_v1;
361e2524b2eSHans Petter Selasky 		const struct usb_audio_mixer_unit_0 *mu_v1;
362e2524b2eSHans Petter Selasky 		const struct usb_audio_selector_unit *su_v1;
363e2524b2eSHans Petter Selasky 		const struct usb_audio_feature_unit *fu_v1;
364e2524b2eSHans Petter Selasky 		const struct usb_audio_processing_unit_0 *pu_v1;
365e2524b2eSHans Petter Selasky 		const struct usb_audio_extension_unit_0 *eu_v1;
366e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_source_unit *csrc_v2;
367e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_selector_unit_0 *csel_v2;
368e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_multiplier_unit *cmul_v2;
369e2524b2eSHans Petter Selasky 		const struct usb_audio20_input_terminal *it_v2;
370e2524b2eSHans Petter Selasky 		const struct usb_audio20_output_terminal *ot_v2;
371e2524b2eSHans Petter Selasky 		const struct usb_audio20_mixer_unit_0 *mu_v2;
372e2524b2eSHans Petter Selasky 		const struct usb_audio20_selector_unit *su_v2;
373e2524b2eSHans Petter Selasky 		const struct usb_audio20_feature_unit *fu_v2;
374e2524b2eSHans Petter Selasky 		const struct usb_audio20_sample_rate_unit *ru_v2;
375e2524b2eSHans Petter Selasky 		const struct usb_audio20_processing_unit_0 *pu_v2;
376e2524b2eSHans Petter Selasky 		const struct usb_audio20_extension_unit_0 *eu_v2;
377e2524b2eSHans Petter Selasky 		const struct usb_audio20_effect_unit *ef_v2;
3783a3f90c6SAndrew Thompson 	}	u;
3793a3f90c6SAndrew Thompson 	struct uaudio_search_result usr;
3803a3f90c6SAndrew Thompson 	struct uaudio_terminal_node *root;
3813a3f90c6SAndrew Thompson };
3823a3f90c6SAndrew Thompson 
3833a3f90c6SAndrew Thompson struct uaudio_format {
3843a3f90c6SAndrew Thompson 	uint16_t wFormat;
3853a3f90c6SAndrew Thompson 	uint8_t	bPrecision;
3863a3f90c6SAndrew Thompson 	uint32_t freebsd_fmt;
3873a3f90c6SAndrew Thompson 	const char *description;
3883a3f90c6SAndrew Thompson };
3893a3f90c6SAndrew Thompson 
390e2524b2eSHans Petter Selasky static const struct uaudio_format uaudio10_formats[] = {
3913a3f90c6SAndrew Thompson 
3923a3f90c6SAndrew Thompson 	{UA_FMT_PCM8, 8, AFMT_U8, "8-bit U-LE PCM"},
3933a3f90c6SAndrew Thompson 	{UA_FMT_PCM8, 16, AFMT_U16_LE, "16-bit U-LE PCM"},
3943a3f90c6SAndrew Thompson 	{UA_FMT_PCM8, 24, AFMT_U24_LE, "24-bit U-LE PCM"},
3953a3f90c6SAndrew Thompson 	{UA_FMT_PCM8, 32, AFMT_U32_LE, "32-bit U-LE PCM"},
3963a3f90c6SAndrew Thompson 
3973a3f90c6SAndrew Thompson 	{UA_FMT_PCM, 8, AFMT_S8, "8-bit S-LE PCM"},
3983a3f90c6SAndrew Thompson 	{UA_FMT_PCM, 16, AFMT_S16_LE, "16-bit S-LE PCM"},
3993a3f90c6SAndrew Thompson 	{UA_FMT_PCM, 24, AFMT_S24_LE, "24-bit S-LE PCM"},
4003a3f90c6SAndrew Thompson 	{UA_FMT_PCM, 32, AFMT_S32_LE, "32-bit S-LE PCM"},
4013a3f90c6SAndrew Thompson 
4023a3f90c6SAndrew Thompson 	{UA_FMT_ALAW, 8, AFMT_A_LAW, "8-bit A-Law"},
4033a3f90c6SAndrew Thompson 	{UA_FMT_MULAW, 8, AFMT_MU_LAW, "8-bit mu-Law"},
4043a3f90c6SAndrew Thompson 
4053a3f90c6SAndrew Thompson 	{0, 0, 0, NULL}
4063a3f90c6SAndrew Thompson };
4073a3f90c6SAndrew Thompson 
408e2524b2eSHans Petter Selasky static const struct uaudio_format uaudio20_formats[] = {
409e2524b2eSHans Petter Selasky 
410e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM, 8, AFMT_S8, "8-bit S-LE PCM"},
411e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM, 16, AFMT_S16_LE, "16-bit S-LE PCM"},
412e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM, 24, AFMT_S24_LE, "24-bit S-LE PCM"},
413e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM, 32, AFMT_S32_LE, "32-bit S-LE PCM"},
414e2524b2eSHans Petter Selasky 
415e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM8, 8, AFMT_U8, "8-bit U-LE PCM"},
416e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM8, 16, AFMT_U16_LE, "16-bit U-LE PCM"},
417e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM8, 24, AFMT_U24_LE, "24-bit U-LE PCM"},
418e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM8, 32, AFMT_U32_LE, "32-bit U-LE PCM"},
419e2524b2eSHans Petter Selasky 
420e2524b2eSHans Petter Selasky 	{UA20_FMT_ALAW, 8, AFMT_A_LAW, "8-bit A-Law"},
421e2524b2eSHans Petter Selasky 	{UA20_FMT_MULAW, 8, AFMT_MU_LAW, "8-bit mu-Law"},
422e2524b2eSHans Petter Selasky 
423e2524b2eSHans Petter Selasky 	{0, 0, 0, NULL}
424e2524b2eSHans Petter Selasky };
425e2524b2eSHans Petter Selasky 
4263a3f90c6SAndrew Thompson #define	UAC_OUTPUT	0
4273a3f90c6SAndrew Thompson #define	UAC_INPUT	1
4283a3f90c6SAndrew Thompson #define	UAC_EQUAL	2
4293a3f90c6SAndrew Thompson #define	UAC_RECORD	3
4303a3f90c6SAndrew Thompson #define	UAC_NCLASSES	4
4313a3f90c6SAndrew Thompson 
432b850ecc1SAndrew Thompson #ifdef USB_DEBUG
4333a3f90c6SAndrew Thompson static const char *uac_names[] = {
4343a3f90c6SAndrew Thompson 	"outputs", "inputs", "equalization", "record"
4353a3f90c6SAndrew Thompson };
4363a3f90c6SAndrew Thompson 
4373a3f90c6SAndrew Thompson #endif
4383a3f90c6SAndrew Thompson 
4393a3f90c6SAndrew Thompson /* prototypes */
4403a3f90c6SAndrew Thompson 
4413a3f90c6SAndrew Thompson static device_probe_t uaudio_probe;
4423a3f90c6SAndrew Thompson static device_attach_t uaudio_attach;
4433a3f90c6SAndrew Thompson static device_detach_t uaudio_detach;
4443a3f90c6SAndrew Thompson 
445e0a69b51SAndrew Thompson static usb_callback_t uaudio_chan_play_callback;
446b4380da7SHans Petter Selasky static usb_callback_t uaudio_chan_play_sync_callback;
447e0a69b51SAndrew Thompson static usb_callback_t uaudio_chan_record_callback;
448b4380da7SHans Petter Selasky static usb_callback_t uaudio_chan_record_sync_callback;
449e0a69b51SAndrew Thompson static usb_callback_t uaudio_mixer_write_cfg_callback;
450e0a69b51SAndrew Thompson static usb_callback_t umidi_bulk_read_callback;
451e0a69b51SAndrew Thompson static usb_callback_t umidi_bulk_write_callback;
45276b71212SHans Petter Selasky static usb_callback_t uaudio_hid_rx_callback;
4533a3f90c6SAndrew Thompson 
454455a367fSHans Petter Selasky static usb_proc_callback_t uaudio_configure_msg;
455455a367fSHans Petter Selasky 
456902514f6SHans Petter Selasky /* ==== USB mixer ==== */
457902514f6SHans Petter Selasky 
458902514f6SHans Petter Selasky static int uaudio_mixer_sysctl_handler(SYSCTL_HANDLER_ARGS);
459902514f6SHans Petter Selasky static void uaudio_mixer_ctl_free(struct uaudio_softc *);
460902514f6SHans Petter Selasky static void uaudio_mixer_register_sysctl(struct uaudio_softc *, device_t);
461902514f6SHans Petter Selasky static void uaudio_mixer_reload_all(struct uaudio_softc *);
462ff4d5953SHans Petter Selasky static void uaudio_mixer_controls_create_ftu(struct uaudio_softc *);
463902514f6SHans Petter Selasky 
464e2524b2eSHans Petter Selasky /* ==== USB audio v1.0 ==== */
465e2524b2eSHans Petter Selasky 
4663a3f90c6SAndrew Thompson static void	uaudio_mixer_add_mixer(struct uaudio_softc *,
4673a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
4683a3f90c6SAndrew Thompson static void	uaudio_mixer_add_selector(struct uaudio_softc *,
4693a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
4703a3f90c6SAndrew Thompson static uint32_t	uaudio_mixer_feature_get_bmaControls(
4714c21be9bSRebecca Cran 		    const struct usb_audio_feature_unit *, uint8_t);
4723a3f90c6SAndrew Thompson static void	uaudio_mixer_add_feature(struct uaudio_softc *,
4733a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
4743a3f90c6SAndrew Thompson static void	uaudio_mixer_add_processing_updown(struct uaudio_softc *,
4753a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
4763a3f90c6SAndrew Thompson static void	uaudio_mixer_add_processing(struct uaudio_softc *,
4773a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
4783a3f90c6SAndrew Thompson static void	uaudio_mixer_add_extension(struct uaudio_softc *,
4793a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
4804c21be9bSRebecca Cran static struct	usb_audio_cluster uaudio_mixer_get_cluster(uint8_t,
4813a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *);
4823a3f90c6SAndrew Thompson static uint16_t	uaudio_mixer_determine_class(const struct uaudio_terminal_node *,
4833a3f90c6SAndrew Thompson 		    struct uaudio_mixer_node *);
4843a3f90c6SAndrew Thompson static uint16_t	uaudio_mixer_feature_name(const struct uaudio_terminal_node *,
4853a3f90c6SAndrew Thompson 		    struct uaudio_mixer_node *);
486e2524b2eSHans Petter Selasky static void	uaudio_mixer_find_inputs_sub(struct uaudio_terminal_node *,
487e2524b2eSHans Petter Selasky 		    const uint8_t *, uint8_t, struct uaudio_search_result *);
488e2524b2eSHans Petter Selasky static const void *uaudio_mixer_verify_desc(const void *, uint32_t);
489e2524b2eSHans Petter Selasky static usb_error_t uaudio_set_speed(struct usb_device *, uint8_t, uint32_t);
490e2524b2eSHans Petter Selasky static int	uaudio_mixer_get(struct usb_device *, uint16_t, uint8_t,
491e2524b2eSHans Petter Selasky 		    struct uaudio_mixer_node *);
492e2524b2eSHans Petter Selasky 
493e2524b2eSHans Petter Selasky /* ==== USB audio v2.0 ==== */
494e2524b2eSHans Petter Selasky 
495e2524b2eSHans Petter Selasky static void	uaudio20_mixer_add_mixer(struct uaudio_softc *,
496e2524b2eSHans Petter Selasky 		    const struct uaudio_terminal_node *, int);
497e2524b2eSHans Petter Selasky static void	uaudio20_mixer_add_selector(struct uaudio_softc *,
498e2524b2eSHans Petter Selasky 		    const struct uaudio_terminal_node *, int);
499e2524b2eSHans Petter Selasky static void	uaudio20_mixer_add_feature(struct uaudio_softc *,
500e2524b2eSHans Petter Selasky 		    const struct uaudio_terminal_node *, int);
501e2524b2eSHans Petter Selasky static struct	usb_audio20_cluster uaudio20_mixer_get_cluster(uint8_t,
502e2524b2eSHans Petter Selasky 		    const struct uaudio_terminal_node *);
503e2524b2eSHans Petter Selasky static uint16_t	uaudio20_mixer_determine_class(const struct uaudio_terminal_node *,
504e2524b2eSHans Petter Selasky 		    struct uaudio_mixer_node *);
505e2524b2eSHans Petter Selasky static uint16_t	uaudio20_mixer_feature_name(const struct uaudio_terminal_node *,
506e2524b2eSHans Petter Selasky 		    struct uaudio_mixer_node *);
507e2524b2eSHans Petter Selasky static void	uaudio20_mixer_find_inputs_sub(struct uaudio_terminal_node *,
508e2524b2eSHans Petter Selasky 		    const uint8_t *, uint8_t, struct uaudio_search_result *);
509e2524b2eSHans Petter Selasky static const void *uaudio20_mixer_verify_desc(const void *, uint32_t);
510e2524b2eSHans Petter Selasky static usb_error_t uaudio20_set_speed(struct usb_device *, uint8_t,
511e2524b2eSHans Petter Selasky 		    uint8_t, uint32_t);
512e2524b2eSHans Petter Selasky 
513e2524b2eSHans Petter Selasky /* USB audio v1.0 and v2.0 */
514e2524b2eSHans Petter Selasky 
515e2524b2eSHans Petter Selasky static void	uaudio_chan_fill_info_sub(struct uaudio_softc *,
516e2524b2eSHans Petter Selasky 		    struct usb_device *, uint32_t, uint8_t, uint8_t);
517e2524b2eSHans Petter Selasky static void	uaudio_chan_fill_info(struct uaudio_softc *,
518e2524b2eSHans Petter Selasky 		    struct usb_device *);
519e2524b2eSHans Petter Selasky static void	uaudio_mixer_add_ctl_sub(struct uaudio_softc *,
520e2524b2eSHans Petter Selasky 		    struct uaudio_mixer_node *);
521e2524b2eSHans Petter Selasky static void	uaudio_mixer_add_ctl(struct uaudio_softc *,
522e2524b2eSHans Petter Selasky 		    struct uaudio_mixer_node *);
523e2524b2eSHans Petter Selasky static void	uaudio_mixer_fill_info(struct uaudio_softc *,
524e2524b2eSHans Petter Selasky 		    struct usb_device *, void *);
525e2524b2eSHans Petter Selasky static void	uaudio_mixer_ctl_set(struct uaudio_softc *,
526e2524b2eSHans Petter Selasky 		    struct uaudio_mixer_node *, uint8_t, int32_t val);
527e2524b2eSHans Petter Selasky static int	uaudio_mixer_signext(uint8_t, int);
528e2524b2eSHans Petter Selasky static int	uaudio_mixer_bsd2value(struct uaudio_mixer_node *, int32_t val);
529e2524b2eSHans Petter Selasky static void	uaudio_mixer_init(struct uaudio_softc *);
5303a3f90c6SAndrew Thompson static const struct uaudio_terminal_node *uaudio_mixer_get_input(
5313a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, uint8_t);
5323a3f90c6SAndrew Thompson static const struct uaudio_terminal_node *uaudio_mixer_get_output(
5333a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, uint8_t);
5343a3f90c6SAndrew Thompson static void	uaudio_mixer_find_outputs_sub(struct uaudio_terminal_node *,
5353a3f90c6SAndrew Thompson 		    uint8_t, uint8_t, struct uaudio_search_result *);
5363a3f90c6SAndrew Thompson static uint8_t	umidi_convert_to_usb(struct umidi_sub_chan *, uint8_t, uint8_t);
537760bc48eSAndrew Thompson static struct	umidi_sub_chan *umidi_sub_by_fifo(struct usb_fifo *);
538760bc48eSAndrew Thompson static void	umidi_start_read(struct usb_fifo *);
539760bc48eSAndrew Thompson static void	umidi_stop_read(struct usb_fifo *);
540760bc48eSAndrew Thompson static void	umidi_start_write(struct usb_fifo *);
541760bc48eSAndrew Thompson static void	umidi_stop_write(struct usb_fifo *);
542760bc48eSAndrew Thompson static int	umidi_open(struct usb_fifo *, int);
543760bc48eSAndrew Thompson static int	umidi_ioctl(struct usb_fifo *, u_long cmd, void *, int);
544760bc48eSAndrew Thompson static void	umidi_close(struct usb_fifo *, int);
5453a3f90c6SAndrew Thompson static void	umidi_init(device_t dev);
54625b74dabSHans Petter Selasky static int	umidi_probe(device_t dev);
54725b74dabSHans Petter Selasky static int	umidi_detach(device_t dev);
54876b71212SHans Petter Selasky static int	uaudio_hid_probe(struct uaudio_softc *sc,
54976b71212SHans Petter Selasky 		    struct usb_attach_arg *uaa);
55076b71212SHans Petter Selasky static void	uaudio_hid_detach(struct uaudio_softc *sc);
5513a3f90c6SAndrew Thompson 
552b850ecc1SAndrew Thompson #ifdef USB_DEBUG
5533a3f90c6SAndrew Thompson static void	uaudio_chan_dump_ep_desc(
5544c21be9bSRebecca Cran 		    const usb_endpoint_descriptor_audio_t *);
5553a3f90c6SAndrew Thompson #endif
5563a3f90c6SAndrew Thompson 
557760bc48eSAndrew Thompson static const struct usb_config
558b4380da7SHans Petter Selasky 	uaudio_cfg_record[UAUDIO_NCHANBUFS + 1] = {
5593a3f90c6SAndrew Thompson 	[0] = {
5603a3f90c6SAndrew Thompson 		.type = UE_ISOCHRONOUS,
5613a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
5623a3f90c6SAndrew Thompson 		.direction = UE_DIR_IN,
5634eae601eSAndrew Thompson 		.bufsize = 0,	/* use "wMaxPacketSize * frames" */
564b029f6bbSAndrew Thompson 		.frames = UAUDIO_NFRAMES,
5654eae601eSAndrew Thompson 		.flags = {.short_xfer_ok = 1,},
5664eae601eSAndrew Thompson 		.callback = &uaudio_chan_record_callback,
5673a3f90c6SAndrew Thompson 	},
5683a3f90c6SAndrew Thompson 
5693a3f90c6SAndrew Thompson 	[1] = {
5703a3f90c6SAndrew Thompson 		.type = UE_ISOCHRONOUS,
5713a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
5723a3f90c6SAndrew Thompson 		.direction = UE_DIR_IN,
5734eae601eSAndrew Thompson 		.bufsize = 0,	/* use "wMaxPacketSize * frames" */
574b029f6bbSAndrew Thompson 		.frames = UAUDIO_NFRAMES,
5754eae601eSAndrew Thompson 		.flags = {.short_xfer_ok = 1,},
5764eae601eSAndrew Thompson 		.callback = &uaudio_chan_record_callback,
5773a3f90c6SAndrew Thompson 	},
578b4380da7SHans Petter Selasky 
579b4380da7SHans Petter Selasky 	[2] = {
580b4380da7SHans Petter Selasky 		.type = UE_ISOCHRONOUS,
581b4380da7SHans Petter Selasky 		.endpoint = UE_ADDR_ANY,
582b4380da7SHans Petter Selasky 		.direction = UE_DIR_OUT,
583b4380da7SHans Petter Selasky 		.bufsize = 0,	/* use "wMaxPacketSize * frames" */
584b4380da7SHans Petter Selasky 		.frames = 1,
585b4380da7SHans Petter Selasky 		.flags = {.no_pipe_ok = 1,.short_xfer_ok = 1,},
586b4380da7SHans Petter Selasky 		.callback = &uaudio_chan_record_sync_callback,
587b4380da7SHans Petter Selasky 	},
5883a3f90c6SAndrew Thompson };
5893a3f90c6SAndrew Thompson 
590760bc48eSAndrew Thompson static const struct usb_config
591b4380da7SHans Petter Selasky 	uaudio_cfg_play[UAUDIO_NCHANBUFS + 1] = {
5923a3f90c6SAndrew Thompson 	[0] = {
5933a3f90c6SAndrew Thompson 		.type = UE_ISOCHRONOUS,
5943a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
5953a3f90c6SAndrew Thompson 		.direction = UE_DIR_OUT,
5964eae601eSAndrew Thompson 		.bufsize = 0,	/* use "wMaxPacketSize * frames" */
597b029f6bbSAndrew Thompson 		.frames = UAUDIO_NFRAMES,
5984eae601eSAndrew Thompson 		.flags = {.short_xfer_ok = 1,},
5994eae601eSAndrew Thompson 		.callback = &uaudio_chan_play_callback,
6003a3f90c6SAndrew Thompson 	},
6013a3f90c6SAndrew Thompson 
6023a3f90c6SAndrew Thompson 	[1] = {
6033a3f90c6SAndrew Thompson 		.type = UE_ISOCHRONOUS,
6043a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
6053a3f90c6SAndrew Thompson 		.direction = UE_DIR_OUT,
6064eae601eSAndrew Thompson 		.bufsize = 0,	/* use "wMaxPacketSize * frames" */
607b029f6bbSAndrew Thompson 		.frames = UAUDIO_NFRAMES,
6084eae601eSAndrew Thompson 		.flags = {.short_xfer_ok = 1,},
6094eae601eSAndrew Thompson 		.callback = &uaudio_chan_play_callback,
6103a3f90c6SAndrew Thompson 	},
611b4380da7SHans Petter Selasky 
612b4380da7SHans Petter Selasky 	[2] = {
613b4380da7SHans Petter Selasky 		.type = UE_ISOCHRONOUS,
614b4380da7SHans Petter Selasky 		.endpoint = UE_ADDR_ANY,
615b4380da7SHans Petter Selasky 		.direction = UE_DIR_IN,
616b4380da7SHans Petter Selasky 		.bufsize = 0,	/* use "wMaxPacketSize * frames" */
617b4380da7SHans Petter Selasky 		.frames = 1,
618b4380da7SHans Petter Selasky 		.flags = {.no_pipe_ok = 1,.short_xfer_ok = 1,},
619b4380da7SHans Petter Selasky 		.callback = &uaudio_chan_play_sync_callback,
620b4380da7SHans Petter Selasky 	},
6213a3f90c6SAndrew Thompson };
6223a3f90c6SAndrew Thompson 
623760bc48eSAndrew Thompson static const struct usb_config
6243a3f90c6SAndrew Thompson 	uaudio_mixer_config[1] = {
6253a3f90c6SAndrew Thompson 	[0] = {
6263a3f90c6SAndrew Thompson 		.type = UE_CONTROL,
6273a3f90c6SAndrew Thompson 		.endpoint = 0x00,	/* Control pipe */
6283a3f90c6SAndrew Thompson 		.direction = UE_DIR_ANY,
629760bc48eSAndrew Thompson 		.bufsize = (sizeof(struct usb_device_request) + 4),
6304eae601eSAndrew Thompson 		.callback = &uaudio_mixer_write_cfg_callback,
6314eae601eSAndrew Thompson 		.timeout = 1000,	/* 1 second */
6323a3f90c6SAndrew Thompson 	},
6333a3f90c6SAndrew Thompson };
6343a3f90c6SAndrew Thompson 
6353a3f90c6SAndrew Thompson static const
6363a3f90c6SAndrew Thompson uint8_t	umidi_cmd_to_len[16] = {
6373a3f90c6SAndrew Thompson 	[0x0] = 0,			/* reserved */
6383a3f90c6SAndrew Thompson 	[0x1] = 0,			/* reserved */
6393a3f90c6SAndrew Thompson 	[0x2] = 2,			/* bytes */
6403a3f90c6SAndrew Thompson 	[0x3] = 3,			/* bytes */
6413a3f90c6SAndrew Thompson 	[0x4] = 3,			/* bytes */
6423a3f90c6SAndrew Thompson 	[0x5] = 1,			/* bytes */
6433a3f90c6SAndrew Thompson 	[0x6] = 2,			/* bytes */
6443a3f90c6SAndrew Thompson 	[0x7] = 3,			/* bytes */
6453a3f90c6SAndrew Thompson 	[0x8] = 3,			/* bytes */
6463a3f90c6SAndrew Thompson 	[0x9] = 3,			/* bytes */
6473a3f90c6SAndrew Thompson 	[0xA] = 3,			/* bytes */
6483a3f90c6SAndrew Thompson 	[0xB] = 3,			/* bytes */
6493a3f90c6SAndrew Thompson 	[0xC] = 2,			/* bytes */
6503a3f90c6SAndrew Thompson 	[0xD] = 2,			/* bytes */
6513a3f90c6SAndrew Thompson 	[0xE] = 3,			/* bytes */
6523a3f90c6SAndrew Thompson 	[0xF] = 1,			/* bytes */
6533a3f90c6SAndrew Thompson };
6543a3f90c6SAndrew Thompson 
655760bc48eSAndrew Thompson static const struct usb_config
6563a3f90c6SAndrew Thompson 	umidi_config[UMIDI_N_TRANSFER] = {
6576f068a43SHans Petter Selasky 	[UMIDI_TX_TRANSFER] = {
6583a3f90c6SAndrew Thompson 		.type = UE_BULK,
6593a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
6603a3f90c6SAndrew Thompson 		.direction = UE_DIR_OUT,
661910f1dcfSHans Petter Selasky 		.bufsize = UMIDI_TX_BUFFER,
6624eae601eSAndrew Thompson 		.callback = &umidi_bulk_write_callback,
6633a3f90c6SAndrew Thompson 	},
6643a3f90c6SAndrew Thompson 
6656f068a43SHans Petter Selasky 	[UMIDI_RX_TRANSFER] = {
6663a3f90c6SAndrew Thompson 		.type = UE_BULK,
6673a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
6683a3f90c6SAndrew Thompson 		.direction = UE_DIR_IN,
6693851442cSAndrew Thompson 		.bufsize = 4,	/* bytes */
670910f1dcfSHans Petter Selasky 		.flags = {.short_xfer_ok = 1,.proxy_buffer = 1,},
6714eae601eSAndrew Thompson 		.callback = &umidi_bulk_read_callback,
6723a3f90c6SAndrew Thompson 	},
6733a3f90c6SAndrew Thompson };
6743a3f90c6SAndrew Thompson 
67576b71212SHans Petter Selasky static const struct usb_config
67676b71212SHans Petter Selasky 	uaudio_hid_config[UAUDIO_HID_N_TRANSFER] = {
67776b71212SHans Petter Selasky 	[UAUDIO_HID_RX_TRANSFER] = {
67876b71212SHans Petter Selasky 		.type = UE_INTERRUPT,
67976b71212SHans Petter Selasky 		.endpoint = UE_ADDR_ANY,
68076b71212SHans Petter Selasky 		.direction = UE_DIR_IN,
68176b71212SHans Petter Selasky 		.bufsize = 0,	/* use wMaxPacketSize */
68276b71212SHans Petter Selasky 		.flags = {.short_xfer_ok = 1,},
68376b71212SHans Petter Selasky 		.callback = &uaudio_hid_rx_callback,
68476b71212SHans Petter Selasky 	},
68576b71212SHans Petter Selasky };
68676b71212SHans Petter Selasky 
6873a3f90c6SAndrew Thompson static devclass_t uaudio_devclass;
6883a3f90c6SAndrew Thompson 
6893a3f90c6SAndrew Thompson static device_method_t uaudio_methods[] = {
6903a3f90c6SAndrew Thompson 	DEVMETHOD(device_probe, uaudio_probe),
6913a3f90c6SAndrew Thompson 	DEVMETHOD(device_attach, uaudio_attach),
6923a3f90c6SAndrew Thompson 	DEVMETHOD(device_detach, uaudio_detach),
6933a3f90c6SAndrew Thompson 	DEVMETHOD(device_suspend, bus_generic_suspend),
6943a3f90c6SAndrew Thompson 	DEVMETHOD(device_resume, bus_generic_resume),
6953a3f90c6SAndrew Thompson 	DEVMETHOD(device_shutdown, bus_generic_shutdown),
6964b7ec270SMarius Strobl 
6974b7ec270SMarius Strobl 	DEVMETHOD_END
6983a3f90c6SAndrew Thompson };
6993a3f90c6SAndrew Thompson 
7003a3f90c6SAndrew Thompson static driver_t uaudio_driver = {
7013a3f90c6SAndrew Thompson 	.name = "uaudio",
7023a3f90c6SAndrew Thompson 	.methods = uaudio_methods,
7033a3f90c6SAndrew Thompson 	.size = sizeof(struct uaudio_softc),
7043a3f90c6SAndrew Thompson };
7053a3f90c6SAndrew Thompson 
70646f0b27aSHans Petter Selasky /* The following table is derived from Linux's quirks-table.h */
70746f0b27aSHans Petter Selasky static const STRUCT_USB_HOST_ID uaudio_vendor_midi[] = {
70846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1000, 0) }, /* UX256 */
70946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1001, 0) }, /* MU1000 */
71046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1002, 0) }, /* MU2000 */
71146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1003, 0) }, /* MU500 */
71246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1004, 3) }, /* UW500 */
71346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1005, 0) }, /* MOTIF6 */
71446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1006, 0) }, /* MOTIF7 */
71546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1007, 0) }, /* MOTIF8 */
71646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1008, 0) }, /* UX96 */
71746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1009, 0) }, /* UX16 */
71846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x100a, 3) }, /* EOS BX */
71946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x100c, 0) }, /* UC-MX */
72046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x100d, 0) }, /* UC-KX */
72146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x100e, 0) }, /* S08 */
72246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x100f, 0) }, /* CLP-150 */
72346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1010, 0) }, /* CLP-170 */
72446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1011, 0) }, /* P-250 */
72546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1012, 0) }, /* TYROS */
72646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1013, 0) }, /* PF-500 */
72746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1014, 0) }, /* S90 */
72846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1015, 0) }, /* MOTIF-R */
72946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1016, 0) }, /* MDP-5 */
73046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1017, 0) }, /* CVP-204 */
73146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1018, 0) }, /* CVP-206 */
73246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1019, 0) }, /* CVP-208 */
73346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x101a, 0) }, /* CVP-210 */
73446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x101b, 0) }, /* PSR-1100 */
73546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x101c, 0) }, /* PSR-2100 */
73646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x101d, 0) }, /* CLP-175 */
73746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x101e, 0) }, /* PSR-K1 */
73846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x101f, 0) }, /* EZ-J24 */
73946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1020, 0) }, /* EZ-250i */
74046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1021, 0) }, /* MOTIF ES 6 */
74146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1022, 0) }, /* MOTIF ES 7 */
74246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1023, 0) }, /* MOTIF ES 8 */
74346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1024, 0) }, /* CVP-301 */
74446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1025, 0) }, /* CVP-303 */
74546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1026, 0) }, /* CVP-305 */
74646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1027, 0) }, /* CVP-307 */
74746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1028, 0) }, /* CVP-309 */
74846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1029, 0) }, /* CVP-309GP */
74946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x102a, 0) }, /* PSR-1500 */
75046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x102b, 0) }, /* PSR-3000 */
75146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x102e, 0) }, /* ELS-01/01C */
75246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1030, 0) }, /* PSR-295/293 */
75346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1031, 0) }, /* DGX-205/203 */
75446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1032, 0) }, /* DGX-305 */
75546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1033, 0) }, /* DGX-505 */
75646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1034, 0) }, /* NULL */
75746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1035, 0) }, /* NULL */
75846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1036, 0) }, /* NULL */
75946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1037, 0) }, /* NULL */
76046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1038, 0) }, /* NULL */
76146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1039, 0) }, /* NULL */
76246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x103a, 0) }, /* NULL */
76346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x103b, 0) }, /* NULL */
76446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x103c, 0) }, /* NULL */
76546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x103d, 0) }, /* NULL */
76646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x103e, 0) }, /* NULL */
76746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x103f, 0) }, /* NULL */
76846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1040, 0) }, /* NULL */
76946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1041, 0) }, /* NULL */
77046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1042, 0) }, /* NULL */
77146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1043, 0) }, /* NULL */
77246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1044, 0) }, /* NULL */
77346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1045, 0) }, /* NULL */
77446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x104e, 0) }, /* NULL */
77546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x104f, 0) }, /* NULL */
77646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1050, 0) }, /* NULL */
77746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1051, 0) }, /* NULL */
77846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1052, 0) }, /* NULL */
77946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1053, 0) }, /* NULL */
78046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1054, 0) }, /* NULL */
78146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1055, 0) }, /* NULL */
78246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1056, 0) }, /* NULL */
78346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1057, 0) }, /* NULL */
78446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1058, 0) }, /* NULL */
78546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1059, 0) }, /* NULL */
78646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x105a, 0) }, /* NULL */
78746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x105b, 0) }, /* NULL */
78846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x105c, 0) }, /* NULL */
78946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x105d, 0) }, /* NULL */
79046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1503, 3) }, /* MOX6/MOX8 */
79146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x2000, 0) }, /* DGP-7 */
79246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x2001, 0) }, /* DGP-5 */
79346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x2002, 0) }, /* NULL */
79446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x2003, 0) }, /* NULL */
79546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5000, 0) }, /* CS1D */
79646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5001, 0) }, /* DSP1D */
79746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5002, 0) }, /* DME32 */
79846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5003, 0) }, /* DM2000 */
79946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5004, 0) }, /* 02R96 */
80046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5005, 0) }, /* ACU16-C */
80146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5006, 0) }, /* NHB32-C */
80246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5007, 0) }, /* DM1000 */
80346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5008, 0) }, /* 01V96 */
80446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5009, 0) }, /* SPX2000 */
80546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x500a, 0) }, /* PM5D */
80646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x500b, 0) }, /* DME64N */
80746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x500c, 0) }, /* DME24N */
80846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x500d, 0) }, /* NULL */
80946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x500e, 0) }, /* NULL */
81046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x500f, 0) }, /* NULL */
81146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x7000, 0) }, /* DTX */
81246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x7010, 0) }, /* UB99 */
81346f0b27aSHans Petter Selasky };
81446f0b27aSHans Petter Selasky 
815f1a16106SHans Petter Selasky static const STRUCT_USB_HOST_ID __used uaudio_devs[] = {
816f1a16106SHans Petter Selasky 	/* Generic USB audio class match */
817f1a16106SHans Petter Selasky 	{USB_IFACE_CLASS(UICLASS_AUDIO),
818f1a16106SHans Petter Selasky 	 USB_IFACE_SUBCLASS(UISUBCLASS_AUDIOCONTROL),},
819f1a16106SHans Petter Selasky 	/* Generic USB MIDI class match */
820f1a16106SHans Petter Selasky 	{USB_IFACE_CLASS(UICLASS_AUDIO),
821f1a16106SHans Petter Selasky 	 USB_IFACE_SUBCLASS(UISUBCLASS_MIDISTREAM),},
822f1a16106SHans Petter Selasky };
823f1a16106SHans Petter Selasky 
8243a3f90c6SAndrew Thompson static int
8253a3f90c6SAndrew Thompson uaudio_probe(device_t dev)
8263a3f90c6SAndrew Thompson {
827760bc48eSAndrew Thompson 	struct usb_attach_arg *uaa = device_get_ivars(dev);
8283a3f90c6SAndrew Thompson 
829fadc970bSAndrew Thompson 	if (uaa->usb_mode != USB_MODE_HOST)
8303a3f90c6SAndrew Thompson 		return (ENXIO);
8313a3f90c6SAndrew Thompson 
83246f0b27aSHans Petter Selasky 	/* lookup non-standard device(s) */
83346f0b27aSHans Petter Selasky 
83446f0b27aSHans Petter Selasky 	if (usbd_lookup_id_by_uaa(uaudio_vendor_midi,
83546f0b27aSHans Petter Selasky 	    sizeof(uaudio_vendor_midi), uaa) == 0) {
83646f0b27aSHans Petter Selasky 		return (BUS_PROBE_SPECIFIC);
83746f0b27aSHans Petter Selasky 	}
8383a3f90c6SAndrew Thompson 
83925b74dabSHans Petter Selasky 	if (uaa->info.bInterfaceClass != UICLASS_AUDIO) {
84076eaf537SHans Petter Selasky 		if (uaa->info.bInterfaceClass != UICLASS_VENDOR ||
84176eaf537SHans Petter Selasky 		    usb_test_quirk(uaa, UQ_AU_VENDOR_CLASS) == 0)
84225b74dabSHans Petter Selasky 			return (ENXIO);
84325b74dabSHans Petter Selasky 	}
84425b74dabSHans Petter Selasky 
84525b74dabSHans Petter Selasky 	/* check for AUDIO control interface */
84625b74dabSHans Petter Selasky 
84725b74dabSHans Petter Selasky 	if (uaa->info.bInterfaceSubClass == UISUBCLASS_AUDIOCONTROL) {
848a593f6b8SAndrew Thompson 		if (usb_test_quirk(uaa, UQ_BAD_AUDIO))
8493a3f90c6SAndrew Thompson 			return (ENXIO);
8503a3f90c6SAndrew Thompson 		else
851cd10bffaSAndriy Gapon 			return (BUS_PROBE_GENERIC);
8523a3f90c6SAndrew Thompson 	}
853dc694251SAndrew Thompson 
854dc694251SAndrew Thompson 	/* check for MIDI stream */
855dc694251SAndrew Thompson 
85625b74dabSHans Petter Selasky 	if (uaa->info.bInterfaceSubClass == UISUBCLASS_MIDISTREAM) {
85725b74dabSHans Petter Selasky 		if (usb_test_quirk(uaa, UQ_BAD_MIDI))
85825b74dabSHans Petter Selasky 			return (ENXIO);
85925b74dabSHans Petter Selasky 		else
860cd10bffaSAndriy Gapon 			return (BUS_PROBE_GENERIC);
861dc694251SAndrew Thompson 	}
8623a3f90c6SAndrew Thompson 	return (ENXIO);
8633a3f90c6SAndrew Thompson }
8643a3f90c6SAndrew Thompson 
8653a3f90c6SAndrew Thompson static int
8663a3f90c6SAndrew Thompson uaudio_attach(device_t dev)
8673a3f90c6SAndrew Thompson {
868760bc48eSAndrew Thompson 	struct usb_attach_arg *uaa = device_get_ivars(dev);
8693a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(dev);
870760bc48eSAndrew Thompson 	struct usb_interface_descriptor *id;
87133da3daaSHans Petter Selasky 	usb_error_t err;
8723a3f90c6SAndrew Thompson 	device_t child;
8733a3f90c6SAndrew Thompson 
8743a3f90c6SAndrew Thompson 	sc->sc_play_chan.priv_sc = sc;
8753a3f90c6SAndrew Thompson 	sc->sc_rec_chan.priv_sc = sc;
8763a3f90c6SAndrew Thompson 	sc->sc_udev = uaa->device;
877b029f6bbSAndrew Thompson 	sc->sc_mixer_iface_index = uaa->info.bIfaceIndex;
878b029f6bbSAndrew Thompson 	sc->sc_mixer_iface_no = uaa->info.bIfaceNum;
879455a367fSHans Petter Selasky 	sc->sc_config_msg[0].hdr.pm_callback = &uaudio_configure_msg;
880455a367fSHans Petter Selasky 	sc->sc_config_msg[0].sc = sc;
881455a367fSHans Petter Selasky 	sc->sc_config_msg[1].hdr.pm_callback = &uaudio_configure_msg;
882455a367fSHans Petter Selasky 	sc->sc_config_msg[1].sc = sc;
8833a3f90c6SAndrew Thompson 
884a593f6b8SAndrew Thompson 	if (usb_test_quirk(uaa, UQ_AUDIO_SWAP_LR))
8853a3f90c6SAndrew Thompson 		sc->sc_uq_audio_swap_lr = 1;
8863a3f90c6SAndrew Thompson 
887a593f6b8SAndrew Thompson 	if (usb_test_quirk(uaa, UQ_AU_INP_ASYNC))
8883a3f90c6SAndrew Thompson 		sc->sc_uq_au_inp_async = 1;
8893a3f90c6SAndrew Thompson 
890a593f6b8SAndrew Thompson 	if (usb_test_quirk(uaa, UQ_AU_NO_XU))
8913a3f90c6SAndrew Thompson 		sc->sc_uq_au_no_xu = 1;
8923a3f90c6SAndrew Thompson 
893a593f6b8SAndrew Thompson 	if (usb_test_quirk(uaa, UQ_BAD_ADC))
8943a3f90c6SAndrew Thompson 		sc->sc_uq_bad_adc = 1;
8953a3f90c6SAndrew Thompson 
89625b74dabSHans Petter Selasky 	if (usb_test_quirk(uaa, UQ_AU_VENDOR_CLASS))
89725b74dabSHans Petter Selasky 		sc->sc_uq_au_vendor_class = 1;
89825b74dabSHans Petter Selasky 
8993a3f90c6SAndrew Thompson 	umidi_init(dev);
9003a3f90c6SAndrew Thompson 
901a593f6b8SAndrew Thompson 	device_set_usb_desc(dev);
9023a3f90c6SAndrew Thompson 
903a593f6b8SAndrew Thompson 	id = usbd_get_interface_descriptor(uaa->iface);
9043a3f90c6SAndrew Thompson 
905e2524b2eSHans Petter Selasky 	/* must fill mixer info before channel info */
9063a3f90c6SAndrew Thompson 	uaudio_mixer_fill_info(sc, uaa->device, id);
9073a3f90c6SAndrew Thompson 
908e2524b2eSHans Petter Selasky 	/* fill channel info */
909e2524b2eSHans Petter Selasky 	uaudio_chan_fill_info(sc, uaa->device);
910e2524b2eSHans Petter Selasky 
9113a3f90c6SAndrew Thompson 	DPRINTF("audio rev %d.%02x\n",
9123a3f90c6SAndrew Thompson 	    sc->sc_audio_rev >> 8,
9133a3f90c6SAndrew Thompson 	    sc->sc_audio_rev & 0xff);
9143a3f90c6SAndrew Thompson 
915ff4d5953SHans Petter Selasky 	if (sc->sc_mixer_count == 0) {
916ff4d5953SHans Petter Selasky 		if (uaa->info.idVendor == USB_VENDOR_MAUDIO &&
917ff4d5953SHans Petter Selasky 		    (uaa->info.idProduct == USB_PRODUCT_MAUDIO_FASTTRACKULTRA ||
918ff4d5953SHans Petter Selasky 		    uaa->info.idProduct == USB_PRODUCT_MAUDIO_FASTTRACKULTRA8R)) {
919ff4d5953SHans Petter Selasky 			DPRINTF("Generating mixer descriptors\n");
920ff4d5953SHans Petter Selasky 			uaudio_mixer_controls_create_ftu(sc);
921ff4d5953SHans Petter Selasky 		}
922ff4d5953SHans Petter Selasky 	}
923ff4d5953SHans Petter Selasky 
9243a3f90c6SAndrew Thompson 	DPRINTF("%d mixer controls\n",
9253a3f90c6SAndrew Thompson 	    sc->sc_mixer_count);
9263a3f90c6SAndrew Thompson 
927455a367fSHans Petter Selasky 	if (sc->sc_play_chan.num_alt > 0) {
928455a367fSHans Petter Selasky 		uint8_t x;
92933da3daaSHans Petter Selasky 
93033da3daaSHans Petter Selasky 		/*
93133da3daaSHans Petter Selasky 		 * Need to set a default alternate interface, else
93233da3daaSHans Petter Selasky 		 * some USB audio devices might go into an infinte
93333da3daaSHans Petter Selasky 		 * re-enumeration loop:
93433da3daaSHans Petter Selasky 		 */
93533da3daaSHans Petter Selasky 		err = usbd_set_alt_interface_index(sc->sc_udev,
93633da3daaSHans Petter Selasky 		    sc->sc_play_chan.usb_alt[0].iface_index,
93733da3daaSHans Petter Selasky 		    sc->sc_play_chan.usb_alt[0].iface_alt_index);
93833da3daaSHans Petter Selasky 		if (err) {
93933da3daaSHans Petter Selasky 			DPRINTF("setting of alternate index failed: %s!\n",
94033da3daaSHans Petter Selasky 			    usbd_errstr(err));
94133da3daaSHans Petter Selasky 		}
942455a367fSHans Petter Selasky 		for (x = 0; x != sc->sc_play_chan.num_alt; x++) {
943902514f6SHans Petter Selasky 			device_printf(dev, "Play: %d Hz, %d ch, %s format, "
944d3e08ca9SHans Petter Selasky 			    "2x8ms buffer.\n",
945455a367fSHans Petter Selasky 			    sc->sc_play_chan.usb_alt[x].sample_rate,
946455a367fSHans Petter Selasky 			    sc->sc_play_chan.usb_alt[x].channels,
947455a367fSHans Petter Selasky 			    sc->sc_play_chan.usb_alt[x].p_fmt->description);
948455a367fSHans Petter Selasky 		}
9493a3f90c6SAndrew Thompson 	} else {
9509b5da816SHans Petter Selasky 		device_printf(dev, "No playback.\n");
9513a3f90c6SAndrew Thompson 	}
9523a3f90c6SAndrew Thompson 
953455a367fSHans Petter Selasky 	if (sc->sc_rec_chan.num_alt > 0) {
954455a367fSHans Petter Selasky 		uint8_t x;
95533da3daaSHans Petter Selasky 
95633da3daaSHans Petter Selasky 		/*
95733da3daaSHans Petter Selasky 		 * Need to set a default alternate interface, else
95833da3daaSHans Petter Selasky 		 * some USB audio devices might go into an infinte
95933da3daaSHans Petter Selasky 		 * re-enumeration loop:
96033da3daaSHans Petter Selasky 		 */
96133da3daaSHans Petter Selasky 		err = usbd_set_alt_interface_index(sc->sc_udev,
96233da3daaSHans Petter Selasky 		    sc->sc_rec_chan.usb_alt[0].iface_index,
96333da3daaSHans Petter Selasky 		    sc->sc_rec_chan.usb_alt[0].iface_alt_index);
96433da3daaSHans Petter Selasky 		if (err) {
96533da3daaSHans Petter Selasky 			DPRINTF("setting of alternate index failed: %s!\n",
96633da3daaSHans Petter Selasky 			    usbd_errstr(err));
96733da3daaSHans Petter Selasky 		}
968455a367fSHans Petter Selasky 		for (x = 0; x != sc->sc_rec_chan.num_alt; x++) {
969902514f6SHans Petter Selasky 			device_printf(dev, "Record: %d Hz, %d ch, %s format, "
970d3e08ca9SHans Petter Selasky 			    "2x8ms buffer.\n",
971455a367fSHans Petter Selasky 			    sc->sc_rec_chan.usb_alt[x].sample_rate,
972455a367fSHans Petter Selasky 			    sc->sc_rec_chan.usb_alt[x].channels,
973455a367fSHans Petter Selasky 			    sc->sc_rec_chan.usb_alt[x].p_fmt->description);
974455a367fSHans Petter Selasky 		}
9753a3f90c6SAndrew Thompson 	} else {
9769b5da816SHans Petter Selasky 		device_printf(dev, "No recording.\n");
9773a3f90c6SAndrew Thompson 	}
9783a3f90c6SAndrew Thompson 
97946f0b27aSHans Petter Selasky 	if (sc->sc_midi_chan.valid == 0) {
98046f0b27aSHans Petter Selasky 		if (usbd_lookup_id_by_uaa(uaudio_vendor_midi,
98146f0b27aSHans Petter Selasky 		    sizeof(uaudio_vendor_midi), uaa) == 0) {
98246f0b27aSHans Petter Selasky 			sc->sc_midi_chan.iface_index =
98346f0b27aSHans Petter Selasky 			    (uint8_t)uaa->driver_info;
98446f0b27aSHans Petter Selasky 			sc->sc_midi_chan.iface_alt_index = 0;
98546f0b27aSHans Petter Selasky 			sc->sc_midi_chan.valid = 1;
98646f0b27aSHans Petter Selasky 		}
98746f0b27aSHans Petter Selasky 	}
98846f0b27aSHans Petter Selasky 
9893a3f90c6SAndrew Thompson 	if (sc->sc_midi_chan.valid) {
9903a3f90c6SAndrew Thompson 
9913a3f90c6SAndrew Thompson 		if (umidi_probe(dev)) {
9923a3f90c6SAndrew Thompson 			goto detach;
9933a3f90c6SAndrew Thompson 		}
9949b5da816SHans Petter Selasky 		device_printf(dev, "MIDI sequencer.\n");
9953a3f90c6SAndrew Thompson 	} else {
99676b71212SHans Petter Selasky 		device_printf(dev, "No MIDI sequencer.\n");
9973a3f90c6SAndrew Thompson 	}
9983a3f90c6SAndrew Thompson 
9993a3f90c6SAndrew Thompson 	DPRINTF("doing child attach\n");
10003a3f90c6SAndrew Thompson 
10013a3f90c6SAndrew Thompson 	/* attach the children */
10023a3f90c6SAndrew Thompson 
10033a3f90c6SAndrew Thompson 	sc->sc_sndcard_func.func = SCF_PCM;
10043a3f90c6SAndrew Thompson 
10059b5da816SHans Petter Selasky 	/*
10069b5da816SHans Petter Selasky 	 * Only attach a PCM device if we have a playback, recording
10079b5da816SHans Petter Selasky 	 * or mixer device present:
10089b5da816SHans Petter Selasky 	 */
1009455a367fSHans Petter Selasky 	if (sc->sc_play_chan.num_alt > 0 ||
1010455a367fSHans Petter Selasky 	    sc->sc_rec_chan.num_alt > 0 ||
10119b5da816SHans Petter Selasky 	    sc->sc_mix_info) {
10123a3f90c6SAndrew Thompson 		child = device_add_child(dev, "pcm", -1);
10133a3f90c6SAndrew Thompson 
10143a3f90c6SAndrew Thompson 		if (child == NULL) {
10153a3f90c6SAndrew Thompson 			DPRINTF("out of memory\n");
10163a3f90c6SAndrew Thompson 			goto detach;
10173a3f90c6SAndrew Thompson 		}
10183a3f90c6SAndrew Thompson 		device_set_ivars(child, &sc->sc_sndcard_func);
10199b5da816SHans Petter Selasky 	}
10203a3f90c6SAndrew Thompson 
10213a3f90c6SAndrew Thompson 	if (bus_generic_attach(dev)) {
10223a3f90c6SAndrew Thompson 		DPRINTF("child attach failed\n");
10233a3f90c6SAndrew Thompson 		goto detach;
10243a3f90c6SAndrew Thompson 	}
1025902514f6SHans Petter Selasky 
102676b71212SHans Petter Selasky 	if (uaudio_hid_probe(sc, uaa) == 0) {
102776b71212SHans Petter Selasky 		device_printf(dev, "HID volume keys found.\n");
102876b71212SHans Petter Selasky 	} else {
102976b71212SHans Petter Selasky 		device_printf(dev, "No HID volume keys found.\n");
103076b71212SHans Petter Selasky 	}
103176b71212SHans Petter Selasky 
1032902514f6SHans Petter Selasky 	/* reload all mixer settings */
1033902514f6SHans Petter Selasky 	uaudio_mixer_reload_all(sc);
1034902514f6SHans Petter Selasky 
10353a3f90c6SAndrew Thompson 	return (0);			/* success */
10363a3f90c6SAndrew Thompson 
10373a3f90c6SAndrew Thompson detach:
10383a3f90c6SAndrew Thompson 	uaudio_detach(dev);
10393a3f90c6SAndrew Thompson 	return (ENXIO);
10403a3f90c6SAndrew Thompson }
10413a3f90c6SAndrew Thompson 
10423a3f90c6SAndrew Thompson static void
10433a3f90c6SAndrew Thompson uaudio_pcm_setflags(device_t dev, uint32_t flags)
10443a3f90c6SAndrew Thompson {
10453a3f90c6SAndrew Thompson 	pcm_setflags(dev, pcm_getflags(dev) | flags);
10463a3f90c6SAndrew Thompson }
10473a3f90c6SAndrew Thompson 
10483a3f90c6SAndrew Thompson int
10493a3f90c6SAndrew Thompson uaudio_attach_sub(device_t dev, kobj_class_t mixer_class, kobj_class_t chan_class)
10503a3f90c6SAndrew Thompson {
10513a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(device_get_parent(dev));
10523a3f90c6SAndrew Thompson 	char status[SND_STATUSLEN];
10533a3f90c6SAndrew Thompson 
10543a3f90c6SAndrew Thompson 	uaudio_mixer_init(sc);
10553a3f90c6SAndrew Thompson 
10563a3f90c6SAndrew Thompson 	if (sc->sc_uq_audio_swap_lr) {
10573a3f90c6SAndrew Thompson 		DPRINTF("hardware has swapped left and right\n");
105890da2b28SAriff Abdullah 		/* uaudio_pcm_setflags(dev, SD_F_PSWAPLR); */
10593a3f90c6SAndrew Thompson 	}
10603a3f90c6SAndrew Thompson 	if (!(sc->sc_mix_info & SOUND_MASK_PCM)) {
10613a3f90c6SAndrew Thompson 
10623a3f90c6SAndrew Thompson 		DPRINTF("emulating master volume\n");
10633a3f90c6SAndrew Thompson 
10643a3f90c6SAndrew Thompson 		/*
10653a3f90c6SAndrew Thompson 		 * Emulate missing pcm mixer controller
10663a3f90c6SAndrew Thompson 		 * through FEEDER_VOLUME
10673a3f90c6SAndrew Thompson 		 */
10683a3f90c6SAndrew Thompson 		uaudio_pcm_setflags(dev, SD_F_SOFTPCMVOL);
10693a3f90c6SAndrew Thompson 	}
1070902514f6SHans Petter Selasky 	if (mixer_init(dev, mixer_class, sc))
10713a3f90c6SAndrew Thompson 		goto detach;
10723a3f90c6SAndrew Thompson 	sc->sc_mixer_init = 1;
10733a3f90c6SAndrew Thompson 
10742ba0f361SHans Petter Selasky 	mixer_hwvol_init(dev);
10752ba0f361SHans Petter Selasky 
10763a3f90c6SAndrew Thompson 	snprintf(status, sizeof(status), "at ? %s", PCM_KLDSTRING(snd_uaudio));
10773a3f90c6SAndrew Thompson 
10783a3f90c6SAndrew Thompson 	if (pcm_register(dev, sc,
1079455a367fSHans Petter Selasky 	    (sc->sc_play_chan.num_alt > 0) ? 1 : 0,
1080455a367fSHans Petter Selasky 	    (sc->sc_rec_chan.num_alt > 0) ? 1 : 0)) {
10813a3f90c6SAndrew Thompson 		goto detach;
10823a3f90c6SAndrew Thompson 	}
108390da2b28SAriff Abdullah 
108490da2b28SAriff Abdullah 	uaudio_pcm_setflags(dev, SD_F_MPSAFE);
10853a3f90c6SAndrew Thompson 	sc->sc_pcm_registered = 1;
10863a3f90c6SAndrew Thompson 
1087455a367fSHans Petter Selasky 	if (sc->sc_play_chan.num_alt > 0) {
10883a3f90c6SAndrew Thompson 		pcm_addchan(dev, PCMDIR_PLAY, chan_class, sc);
10893a3f90c6SAndrew Thompson 	}
1090455a367fSHans Petter Selasky 	if (sc->sc_rec_chan.num_alt > 0) {
10913a3f90c6SAndrew Thompson 		pcm_addchan(dev, PCMDIR_REC, chan_class, sc);
10923a3f90c6SAndrew Thompson 	}
10933a3f90c6SAndrew Thompson 	pcm_setstatus(dev, status);
10943a3f90c6SAndrew Thompson 
1095902514f6SHans Petter Selasky 	uaudio_mixer_register_sysctl(sc, dev);
1096902514f6SHans Petter Selasky 
10973a3f90c6SAndrew Thompson 	return (0);			/* success */
10983a3f90c6SAndrew Thompson 
10993a3f90c6SAndrew Thompson detach:
11003a3f90c6SAndrew Thompson 	uaudio_detach_sub(dev);
11013a3f90c6SAndrew Thompson 	return (ENXIO);
11023a3f90c6SAndrew Thompson }
11033a3f90c6SAndrew Thompson 
11043a3f90c6SAndrew Thompson int
11053a3f90c6SAndrew Thompson uaudio_detach_sub(device_t dev)
11063a3f90c6SAndrew Thompson {
11073a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(device_get_parent(dev));
11083a3f90c6SAndrew Thompson 	int error = 0;
11093a3f90c6SAndrew Thompson 
11103a3f90c6SAndrew Thompson repeat:
11113a3f90c6SAndrew Thompson 	if (sc->sc_pcm_registered) {
11123a3f90c6SAndrew Thompson 		error = pcm_unregister(dev);
11133a3f90c6SAndrew Thompson 	} else {
11143a3f90c6SAndrew Thompson 		if (sc->sc_mixer_init) {
11153a3f90c6SAndrew Thompson 			error = mixer_uninit(dev);
11163a3f90c6SAndrew Thompson 		}
11173a3f90c6SAndrew Thompson 	}
11183a3f90c6SAndrew Thompson 
11193a3f90c6SAndrew Thompson 	if (error) {
11203a3f90c6SAndrew Thompson 		device_printf(dev, "Waiting for sound application to exit!\n");
1121a593f6b8SAndrew Thompson 		usb_pause_mtx(NULL, 2 * hz);
11223a3f90c6SAndrew Thompson 		goto repeat;		/* try again */
11233a3f90c6SAndrew Thompson 	}
11243a3f90c6SAndrew Thompson 	return (0);			/* success */
11253a3f90c6SAndrew Thompson }
11263a3f90c6SAndrew Thompson 
11273a3f90c6SAndrew Thompson static int
11283a3f90c6SAndrew Thompson uaudio_detach(device_t dev)
11293a3f90c6SAndrew Thompson {
11303a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(dev);
11313a3f90c6SAndrew Thompson 
1132a6ed4937SHans Petter Selasky 	/*
1133a6ed4937SHans Petter Selasky 	 * Stop USB transfers early so that any audio applications
1134a6ed4937SHans Petter Selasky 	 * will time out and close opened /dev/dspX.Y device(s), if
1135a6ed4937SHans Petter Selasky 	 * any.
1136a6ed4937SHans Petter Selasky 	 */
1137455a367fSHans Petter Selasky 	usb_proc_explore_lock(sc->sc_udev);
1138455a367fSHans Petter Selasky 	sc->sc_play_chan.operation = CHAN_OP_DRAIN;
1139455a367fSHans Petter Selasky 	sc->sc_rec_chan.operation = CHAN_OP_DRAIN;
1140455a367fSHans Petter Selasky 	usb_proc_explore_mwait(sc->sc_udev,
1141455a367fSHans Petter Selasky 	    &sc->sc_config_msg[0], &sc->sc_config_msg[1]);
1142455a367fSHans Petter Selasky 	usb_proc_explore_unlock(sc->sc_udev);
1143455a367fSHans Petter Selasky 
1144b4380da7SHans Petter Selasky 	usbd_transfer_unsetup(sc->sc_play_chan.xfer, UAUDIO_NCHANBUFS + 1);
1145b4380da7SHans Petter Selasky 	usbd_transfer_unsetup(sc->sc_rec_chan.xfer, UAUDIO_NCHANBUFS + 1);
1146a6ed4937SHans Petter Selasky 
114776b71212SHans Petter Selasky 	uaudio_hid_detach(sc);
114876b71212SHans Petter Selasky 
1149a6ed4937SHans Petter Selasky 	if (bus_generic_detach(dev) != 0) {
11503a3f90c6SAndrew Thompson 		DPRINTF("detach failed!\n");
11513a3f90c6SAndrew Thompson 	}
11523a3f90c6SAndrew Thompson 	sbuf_delete(&sc->sc_sndstat);
11533a3f90c6SAndrew Thompson 	sc->sc_sndstat_valid = 0;
11543a3f90c6SAndrew Thompson 
11553a3f90c6SAndrew Thompson 	umidi_detach(dev);
11563a3f90c6SAndrew Thompson 
1157902514f6SHans Petter Selasky 	/* free mixer data */
1158902514f6SHans Petter Selasky 
1159902514f6SHans Petter Selasky 	uaudio_mixer_ctl_free(sc);
1160902514f6SHans Petter Selasky 
11613a3f90c6SAndrew Thompson 	return (0);
11623a3f90c6SAndrew Thompson }
11633a3f90c6SAndrew Thompson 
1164455a367fSHans Petter Selasky static uint32_t
1165455a367fSHans Petter Selasky uaudio_get_buffer_size(struct uaudio_chan *ch, uint8_t alt)
1166455a367fSHans Petter Selasky {
1167455a367fSHans Petter Selasky 	struct uaudio_chan_alt *chan_alt = &ch->usb_alt[alt];
1168455a367fSHans Petter Selasky 	/* We use 2 times 8ms of buffer */
1169455a367fSHans Petter Selasky 	uint32_t buf_size = (((chan_alt->sample_rate * (UAUDIO_NFRAMES / 8)) +
1170455a367fSHans Petter Selasky 	    1000 - 1) / 1000) * chan_alt->sample_size;
1171455a367fSHans Petter Selasky 	return (buf_size);
1172455a367fSHans Petter Selasky }
1173455a367fSHans Petter Selasky 
1174455a367fSHans Petter Selasky static void
1175455a367fSHans Petter Selasky uaudio_configure_msg_sub(struct uaudio_softc *sc,
1176455a367fSHans Petter Selasky     struct uaudio_chan *chan, int dir)
1177455a367fSHans Petter Selasky {
1178455a367fSHans Petter Selasky 	struct uaudio_chan_alt *chan_alt;
1179455a367fSHans Petter Selasky 	uint32_t frames;
1180455a367fSHans Petter Selasky 	uint32_t buf_size;
1181455a367fSHans Petter Selasky 	uint16_t fps;
1182455a367fSHans Petter Selasky 	uint8_t set_alt;
1183455a367fSHans Petter Selasky 	uint8_t fps_shift;
1184455a367fSHans Petter Selasky 	uint8_t operation;
1185455a367fSHans Petter Selasky 	usb_error_t err;
1186455a367fSHans Petter Selasky 
1187455a367fSHans Petter Selasky 	if (chan->num_alt <= 0)
1188455a367fSHans Petter Selasky 		return;
1189455a367fSHans Petter Selasky 
1190455a367fSHans Petter Selasky 	DPRINTF("\n");
1191455a367fSHans Petter Selasky 
1192455a367fSHans Petter Selasky 	usb_proc_explore_lock(sc->sc_udev);
1193455a367fSHans Petter Selasky 	operation = chan->operation;
1194455a367fSHans Petter Selasky 	chan->operation = CHAN_OP_NONE;
1195455a367fSHans Petter Selasky 	usb_proc_explore_unlock(sc->sc_udev);
1196455a367fSHans Petter Selasky 
1197455a367fSHans Petter Selasky 	mtx_lock(chan->pcm_mtx);
1198455a367fSHans Petter Selasky 	if (chan->cur_alt != chan->set_alt)
1199455a367fSHans Petter Selasky 		set_alt = chan->set_alt;
1200455a367fSHans Petter Selasky 	else
1201455a367fSHans Petter Selasky 		set_alt = CHAN_MAX_ALT;
1202455a367fSHans Petter Selasky 	mtx_unlock(chan->pcm_mtx);
1203455a367fSHans Petter Selasky 
1204455a367fSHans Petter Selasky 	if (set_alt >= chan->num_alt)
1205455a367fSHans Petter Selasky 		goto done;
1206455a367fSHans Petter Selasky 
1207455a367fSHans Petter Selasky 	chan_alt = chan->usb_alt + set_alt;
1208455a367fSHans Petter Selasky 
1209455a367fSHans Petter Selasky 	usbd_transfer_unsetup(chan->xfer, UAUDIO_NCHANBUFS + 1);
1210455a367fSHans Petter Selasky 
1211455a367fSHans Petter Selasky 	err = usbd_set_alt_interface_index(sc->sc_udev,
1212455a367fSHans Petter Selasky 	    chan_alt->iface_index, chan_alt->iface_alt_index);
1213455a367fSHans Petter Selasky 	if (err) {
1214455a367fSHans Petter Selasky 		DPRINTF("setting of alternate index failed: %s!\n",
1215455a367fSHans Petter Selasky 		    usbd_errstr(err));
1216455a367fSHans Petter Selasky 		goto error;
1217455a367fSHans Petter Selasky 	}
1218455a367fSHans Petter Selasky 
1219455a367fSHans Petter Selasky 	/*
1220455a367fSHans Petter Selasky 	 * Only set the sample rate if the channel reports that it
1221455a367fSHans Petter Selasky 	 * supports the frequency control.
1222455a367fSHans Petter Selasky 	 */
1223455a367fSHans Petter Selasky 
1224455a367fSHans Petter Selasky 	if (sc->sc_audio_rev >= UAUDIO_VERSION_30) {
1225455a367fSHans Petter Selasky 		/* FALLTHROUGH */
1226455a367fSHans Petter Selasky 	} else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) {
1227455a367fSHans Petter Selasky 		unsigned int x;
1228455a367fSHans Petter Selasky 
1229455a367fSHans Petter Selasky 		for (x = 0; x != 256; x++) {
1230455a367fSHans Petter Selasky 			if (dir == PCMDIR_PLAY) {
1231455a367fSHans Petter Selasky 				if (!(sc->sc_mixer_clocks.bit_output[x / 8] &
1232455a367fSHans Petter Selasky 				    (1 << (x % 8)))) {
1233455a367fSHans Petter Selasky 					continue;
1234455a367fSHans Petter Selasky 				}
1235455a367fSHans Petter Selasky 			} else {
1236455a367fSHans Petter Selasky 				if (!(sc->sc_mixer_clocks.bit_input[x / 8] &
1237455a367fSHans Petter Selasky 				    (1 << (x % 8)))) {
1238455a367fSHans Petter Selasky 					continue;
1239455a367fSHans Petter Selasky 				}
1240455a367fSHans Petter Selasky 			}
1241455a367fSHans Petter Selasky 
1242455a367fSHans Petter Selasky 			if (uaudio20_set_speed(sc->sc_udev,
1243455a367fSHans Petter Selasky 			    sc->sc_mixer_iface_no, x, chan_alt->sample_rate)) {
1244455a367fSHans Petter Selasky 				/*
1245455a367fSHans Petter Selasky 				 * If the endpoint is adaptive setting
1246455a367fSHans Petter Selasky 				 * the speed may fail.
1247455a367fSHans Petter Selasky 				 */
1248455a367fSHans Petter Selasky 				DPRINTF("setting of sample rate failed! "
1249455a367fSHans Petter Selasky 				    "(continuing anyway)\n");
1250455a367fSHans Petter Selasky 			}
1251455a367fSHans Petter Selasky 		}
1252455a367fSHans Petter Selasky 	} else if (chan_alt->p_sed.v1->bmAttributes & UA_SED_FREQ_CONTROL) {
1253455a367fSHans Petter Selasky 		if (uaudio_set_speed(sc->sc_udev,
1254455a367fSHans Petter Selasky 		    chan_alt->p_ed1->bEndpointAddress, chan_alt->sample_rate)) {
1255455a367fSHans Petter Selasky 			/*
1256455a367fSHans Petter Selasky 			 * If the endpoint is adaptive setting the
1257455a367fSHans Petter Selasky 			 * speed may fail.
1258455a367fSHans Petter Selasky 			 */
1259455a367fSHans Petter Selasky 			DPRINTF("setting of sample rate failed! "
1260455a367fSHans Petter Selasky 			    "(continuing anyway)\n");
1261455a367fSHans Petter Selasky 		}
1262455a367fSHans Petter Selasky 	}
1263455a367fSHans Petter Selasky 	if (usbd_transfer_setup(sc->sc_udev, &chan_alt->iface_index, chan->xfer,
1264455a367fSHans Petter Selasky 	    chan_alt->usb_cfg, UAUDIO_NCHANBUFS + 1, chan, chan->pcm_mtx)) {
1265455a367fSHans Petter Selasky 		DPRINTF("could not allocate USB transfers!\n");
1266455a367fSHans Petter Selasky 		goto error;
1267455a367fSHans Petter Selasky 	}
1268455a367fSHans Petter Selasky 
1269455a367fSHans Petter Selasky 	fps = usbd_get_isoc_fps(sc->sc_udev);
1270455a367fSHans Petter Selasky 
1271455a367fSHans Petter Selasky 	if (fps < 8000) {
1272455a367fSHans Petter Selasky 		/* FULL speed USB */
1273455a367fSHans Petter Selasky 		frames = 8;
1274455a367fSHans Petter Selasky 	} else {
1275455a367fSHans Petter Selasky 		/* HIGH speed USB */
1276455a367fSHans Petter Selasky 		frames = UAUDIO_NFRAMES;
1277455a367fSHans Petter Selasky 	}
1278455a367fSHans Petter Selasky 
1279455a367fSHans Petter Selasky 	fps_shift = usbd_xfer_get_fps_shift(chan->xfer[0]);
1280455a367fSHans Petter Selasky 
1281455a367fSHans Petter Selasky 	/* down shift number of frames per second, if any */
1282455a367fSHans Petter Selasky 	fps >>= fps_shift;
1283455a367fSHans Petter Selasky 	frames >>= fps_shift;
1284455a367fSHans Petter Selasky 
1285455a367fSHans Petter Selasky 	/* bytes per frame should not be zero */
1286455a367fSHans Petter Selasky 	chan->bytes_per_frame[0] =
1287455a367fSHans Petter Selasky 	    ((chan_alt->sample_rate / fps) * chan_alt->sample_size);
1288455a367fSHans Petter Selasky 	chan->bytes_per_frame[1] =
1289455a367fSHans Petter Selasky 	    (((chan_alt->sample_rate + fps - 1) / fps) * chan_alt->sample_size);
1290455a367fSHans Petter Selasky 
1291455a367fSHans Petter Selasky 	/* setup data rate dithering, if any */
1292455a367fSHans Petter Selasky 	chan->frames_per_second = fps;
1293455a367fSHans Petter Selasky 	chan->sample_rem = chan_alt->sample_rate % fps;
1294455a367fSHans Petter Selasky 	chan->sample_curr = 0;
1295455a367fSHans Petter Selasky 	chan->frames_per_second = fps;
1296455a367fSHans Petter Selasky 
1297455a367fSHans Petter Selasky 	/* compute required buffer size */
1298455a367fSHans Petter Selasky 	buf_size = (chan->bytes_per_frame[1] * frames);
1299455a367fSHans Petter Selasky 
1300455a367fSHans Petter Selasky 	if (buf_size > (chan->end - chan->start)) {
1301455a367fSHans Petter Selasky 		DPRINTF("buffer size is too big\n");
1302455a367fSHans Petter Selasky 		goto error;
1303455a367fSHans Petter Selasky 	}
1304455a367fSHans Petter Selasky 
1305455a367fSHans Petter Selasky 	chan->intr_frames = frames;
1306455a367fSHans Petter Selasky 
1307455a367fSHans Petter Selasky 	DPRINTF("fps=%d sample_rem=%d\n", (int)fps, (int)chan->sample_rem);
1308455a367fSHans Petter Selasky 
1309455a367fSHans Petter Selasky 	if (chan->intr_frames == 0) {
1310455a367fSHans Petter Selasky 		DPRINTF("frame shift is too high!\n");
1311455a367fSHans Petter Selasky 		goto error;
1312455a367fSHans Petter Selasky 	}
1313455a367fSHans Petter Selasky 
1314455a367fSHans Petter Selasky 	mtx_lock(chan->pcm_mtx);
1315455a367fSHans Petter Selasky 	chan->cur_alt = set_alt;
1316455a367fSHans Petter Selasky 	mtx_unlock(chan->pcm_mtx);
1317455a367fSHans Petter Selasky 
1318455a367fSHans Petter Selasky done:
1319455a367fSHans Petter Selasky #if (UAUDIO_NCHANBUFS != 2)
1320455a367fSHans Petter Selasky #error "please update code"
1321455a367fSHans Petter Selasky #endif
1322455a367fSHans Petter Selasky 	switch (operation) {
1323455a367fSHans Petter Selasky 	case CHAN_OP_START:
1324455a367fSHans Petter Selasky 		mtx_lock(chan->pcm_mtx);
1325455a367fSHans Petter Selasky 		usbd_transfer_start(chan->xfer[0]);
1326455a367fSHans Petter Selasky 		usbd_transfer_start(chan->xfer[1]);
1327455a367fSHans Petter Selasky 		mtx_unlock(chan->pcm_mtx);
1328455a367fSHans Petter Selasky 		break;
1329455a367fSHans Petter Selasky 	case CHAN_OP_STOP:
1330455a367fSHans Petter Selasky 		mtx_lock(chan->pcm_mtx);
1331455a367fSHans Petter Selasky 		usbd_transfer_stop(chan->xfer[0]);
1332455a367fSHans Petter Selasky 		usbd_transfer_stop(chan->xfer[1]);
1333455a367fSHans Petter Selasky 		mtx_unlock(chan->pcm_mtx);
1334455a367fSHans Petter Selasky 		break;
1335455a367fSHans Petter Selasky 	default:
1336455a367fSHans Petter Selasky 		break;
1337455a367fSHans Petter Selasky 	}
1338455a367fSHans Petter Selasky 	return;
1339455a367fSHans Petter Selasky 
1340455a367fSHans Petter Selasky error:
1341455a367fSHans Petter Selasky 	usbd_transfer_unsetup(chan->xfer, UAUDIO_NCHANBUFS + 1);
1342455a367fSHans Petter Selasky 
1343455a367fSHans Petter Selasky 	mtx_lock(chan->pcm_mtx);
1344455a367fSHans Petter Selasky 	chan->cur_alt = CHAN_MAX_ALT;
1345455a367fSHans Petter Selasky 	mtx_unlock(chan->pcm_mtx);
1346455a367fSHans Petter Selasky }
1347455a367fSHans Petter Selasky 
1348455a367fSHans Petter Selasky static void
1349455a367fSHans Petter Selasky uaudio_configure_msg(struct usb_proc_msg *pm)
1350455a367fSHans Petter Selasky {
1351455a367fSHans Petter Selasky 	struct uaudio_softc *sc = ((struct uaudio_configure_msg *)pm)->sc;
1352455a367fSHans Petter Selasky 
1353455a367fSHans Petter Selasky 	usb_proc_explore_unlock(sc->sc_udev);
1354455a367fSHans Petter Selasky 	uaudio_configure_msg_sub(sc, &sc->sc_play_chan, PCMDIR_PLAY);
1355455a367fSHans Petter Selasky 	uaudio_configure_msg_sub(sc, &sc->sc_rec_chan, PCMDIR_REC);
1356455a367fSHans Petter Selasky 	usb_proc_explore_lock(sc->sc_udev);
1357455a367fSHans Petter Selasky }
1358455a367fSHans Petter Selasky 
13593a3f90c6SAndrew Thompson /*========================================================================*
13603a3f90c6SAndrew Thompson  * AS - Audio Stream - routines
13613a3f90c6SAndrew Thompson  *========================================================================*/
13623a3f90c6SAndrew Thompson 
1363b850ecc1SAndrew Thompson #ifdef USB_DEBUG
13643a3f90c6SAndrew Thompson static void
13654c21be9bSRebecca Cran uaudio_chan_dump_ep_desc(const usb_endpoint_descriptor_audio_t *ed)
13663a3f90c6SAndrew Thompson {
13673a3f90c6SAndrew Thompson 	if (ed) {
13683a3f90c6SAndrew Thompson 		DPRINTF("endpoint=%p bLength=%d bDescriptorType=%d \n"
13693a3f90c6SAndrew Thompson 		    "bEndpointAddress=%d bmAttributes=0x%x \n"
13703a3f90c6SAndrew Thompson 		    "wMaxPacketSize=%d bInterval=%d \n"
13713a3f90c6SAndrew Thompson 		    "bRefresh=%d bSynchAddress=%d\n",
13723a3f90c6SAndrew Thompson 		    ed, ed->bLength, ed->bDescriptorType,
13733a3f90c6SAndrew Thompson 		    ed->bEndpointAddress, ed->bmAttributes,
13743a3f90c6SAndrew Thompson 		    UGETW(ed->wMaxPacketSize), ed->bInterval,
1375e3e05e50SAndrew Thompson 		    UEP_HAS_REFRESH(ed) ? ed->bRefresh : 0,
1376e3e05e50SAndrew Thompson 		    UEP_HAS_SYNCADDR(ed) ? ed->bSynchAddress : 0);
13773a3f90c6SAndrew Thompson 	}
13783a3f90c6SAndrew Thompson }
13793a3f90c6SAndrew Thompson 
13803a3f90c6SAndrew Thompson #endif
13813a3f90c6SAndrew Thompson 
1382f895cc0cSHans Petter Selasky /*
1383f895cc0cSHans Petter Selasky  * The following is a workaround for broken no-name USB audio devices
1384f895cc0cSHans Petter Selasky  * sold by dealextreme called "3D sound". The problem is that the
1385f895cc0cSHans Petter Selasky  * manufacturer computed wMaxPacketSize is too small to hold the
1386f895cc0cSHans Petter Selasky  * actual data sent. In other words the device sometimes sends more
1387f895cc0cSHans Petter Selasky  * data than it actually reports it can send in a single isochronous
1388f895cc0cSHans Petter Selasky  * packet.
1389f895cc0cSHans Petter Selasky  */
1390f895cc0cSHans Petter Selasky static void
1391f895cc0cSHans Petter Selasky uaudio_record_fix_fs(usb_endpoint_descriptor_audio_t *ep,
1392f895cc0cSHans Petter Selasky     uint32_t xps, uint32_t add)
1393f895cc0cSHans Petter Selasky {
1394f895cc0cSHans Petter Selasky 	uint32_t mps;
1395f895cc0cSHans Petter Selasky 
1396f895cc0cSHans Petter Selasky 	mps = UGETW(ep->wMaxPacketSize);
1397f895cc0cSHans Petter Selasky 
1398f895cc0cSHans Petter Selasky 	/*
1399f895cc0cSHans Petter Selasky 	 * If the device indicates it can send more data than what the
1400f895cc0cSHans Petter Selasky 	 * sample rate indicates, we apply the workaround.
1401f895cc0cSHans Petter Selasky 	 */
1402f895cc0cSHans Petter Selasky 	if (mps > xps) {
1403f895cc0cSHans Petter Selasky 
1404f895cc0cSHans Petter Selasky 		/* allow additional data */
1405f895cc0cSHans Petter Selasky 		xps += add;
1406f895cc0cSHans Petter Selasky 
1407f895cc0cSHans Petter Selasky 		/* check against the maximum USB 1.x length */
1408f895cc0cSHans Petter Selasky 		if (xps > 1023)
1409f895cc0cSHans Petter Selasky 			xps = 1023;
1410f895cc0cSHans Petter Selasky 
1411f895cc0cSHans Petter Selasky 		/* check if we should do an update */
1412f895cc0cSHans Petter Selasky 		if (mps < xps) {
1413f895cc0cSHans Petter Selasky 			/* simply update the wMaxPacketSize field */
1414f895cc0cSHans Petter Selasky 			USETW(ep->wMaxPacketSize, xps);
1415f895cc0cSHans Petter Selasky 			DPRINTF("Workaround: Updated wMaxPacketSize "
1416f895cc0cSHans Petter Selasky 			    "from %d to %d bytes.\n",
1417f895cc0cSHans Petter Selasky 			    (int)mps, (int)xps);
1418f895cc0cSHans Petter Selasky 		}
1419f895cc0cSHans Petter Selasky 	}
1420f895cc0cSHans Petter Selasky }
1421f895cc0cSHans Petter Selasky 
1422e2524b2eSHans Petter Selasky static usb_error_t
1423e2524b2eSHans Petter Selasky uaudio20_check_rate(struct usb_device *udev, uint8_t iface_no,
1424e2524b2eSHans Petter Selasky     uint8_t clockid, uint32_t rate)
1425e2524b2eSHans Petter Selasky {
1426e2524b2eSHans Petter Selasky 	struct usb_device_request req;
1427e2524b2eSHans Petter Selasky 	usb_error_t error;
1428e2524b2eSHans Petter Selasky 	uint8_t data[255];
1429e2524b2eSHans Petter Selasky 	uint16_t actlen;
1430e2524b2eSHans Petter Selasky 	uint16_t rates;
1431e2524b2eSHans Petter Selasky 	uint16_t x;
1432e2524b2eSHans Petter Selasky 
1433e2524b2eSHans Petter Selasky 	DPRINTFN(6, "ifaceno=%d clockid=%d rate=%u\n",
1434e2524b2eSHans Petter Selasky 	    iface_no, clockid, rate);
1435e2524b2eSHans Petter Selasky 
1436e2524b2eSHans Petter Selasky 	req.bmRequestType = UT_READ_CLASS_INTERFACE;
1437e2524b2eSHans Petter Selasky 	req.bRequest = UA20_CS_RANGE;
1438e2524b2eSHans Petter Selasky 	USETW2(req.wValue, UA20_CS_SAM_FREQ_CONTROL, 0);
1439e2524b2eSHans Petter Selasky 	USETW2(req.wIndex, clockid, iface_no);
1440e2524b2eSHans Petter Selasky 	USETW(req.wLength, 255);
1441e2524b2eSHans Petter Selasky 
1442e2524b2eSHans Petter Selasky         error = usbd_do_request_flags(udev, NULL, &req, data,
1443e2524b2eSHans Petter Selasky 	    USB_SHORT_XFER_OK, &actlen, USB_DEFAULT_TIMEOUT);
1444e2524b2eSHans Petter Selasky 
1445e2524b2eSHans Petter Selasky 	if (error != 0 || actlen < 2)
1446e2524b2eSHans Petter Selasky 		return (USB_ERR_INVAL);
1447e2524b2eSHans Petter Selasky 
1448e2524b2eSHans Petter Selasky 	rates = data[0] | (data[1] << 8);
1449e2524b2eSHans Petter Selasky 	actlen = (actlen - 2) / 12;
1450e2524b2eSHans Petter Selasky 
1451e2524b2eSHans Petter Selasky 	if (rates > actlen) {
1452e2524b2eSHans Petter Selasky 		DPRINTF("Too many rates\n");
1453e2524b2eSHans Petter Selasky 		rates = actlen;
1454e2524b2eSHans Petter Selasky 	}
1455e2524b2eSHans Petter Selasky 
1456e2524b2eSHans Petter Selasky 	for (x = 0; x != rates; x++) {
1457e2524b2eSHans Petter Selasky 		uint32_t min = UGETDW(data + 2 + (12 * x));
1458e2524b2eSHans Petter Selasky 		uint32_t max = UGETDW(data + 6 + (12 * x));
1459e2524b2eSHans Petter Selasky 		uint32_t res = UGETDW(data + 10 + (12 * x));
1460e2524b2eSHans Petter Selasky 
1461e2524b2eSHans Petter Selasky 		if (res == 0) {
1462e2524b2eSHans Petter Selasky 			DPRINTF("Zero residue\n");
1463e2524b2eSHans Petter Selasky 			res = 1;
1464e2524b2eSHans Petter Selasky 		}
1465e2524b2eSHans Petter Selasky 
1466e2524b2eSHans Petter Selasky 		if (min > max) {
1467e2524b2eSHans Petter Selasky 			DPRINTF("Swapped max and min\n");
1468e2524b2eSHans Petter Selasky 			uint32_t temp;
1469e2524b2eSHans Petter Selasky 			temp = min;
1470e2524b2eSHans Petter Selasky 			min = max;
1471e2524b2eSHans Petter Selasky 			max = temp;
1472e2524b2eSHans Petter Selasky 		}
1473e2524b2eSHans Petter Selasky 
1474e2524b2eSHans Petter Selasky 		if (rate >= min && rate <= max &&
1475e2524b2eSHans Petter Selasky 		    (((rate - min) % res) == 0)) {
1476e2524b2eSHans Petter Selasky 			return (0);
1477e2524b2eSHans Petter Selasky 		}
1478e2524b2eSHans Petter Selasky 	}
1479e2524b2eSHans Petter Selasky 	return (USB_ERR_INVAL);
1480e2524b2eSHans Petter Selasky }
1481e2524b2eSHans Petter Selasky 
14823a3f90c6SAndrew Thompson static void
1483760bc48eSAndrew Thompson uaudio_chan_fill_info_sub(struct uaudio_softc *sc, struct usb_device *udev,
1484afbfddd9SAndrew Thompson     uint32_t rate, uint8_t channels, uint8_t bit_resolution)
14853a3f90c6SAndrew Thompson {
1486760bc48eSAndrew Thompson 	struct usb_descriptor *desc = NULL;
1487e2524b2eSHans Petter Selasky 	union uaudio_asid asid = { NULL };
1488e2524b2eSHans Petter Selasky 	union uaudio_asf1d asf1d = { NULL };
1489e2524b2eSHans Petter Selasky 	union uaudio_sed sed = { NULL };
1490f895cc0cSHans Petter Selasky 	usb_endpoint_descriptor_audio_t *ed1 = NULL;
1491e2524b2eSHans Petter Selasky 	const struct usb_audio_control_descriptor *acdp = NULL;
1492a593f6b8SAndrew Thompson 	struct usb_config_descriptor *cd = usbd_get_config_descriptor(udev);
1493760bc48eSAndrew Thompson 	struct usb_interface_descriptor *id;
1494e2524b2eSHans Petter Selasky 	const struct uaudio_format *p_fmt = NULL;
14953a3f90c6SAndrew Thompson 	struct uaudio_chan *chan;
1496455a367fSHans Petter Selasky 	struct uaudio_chan_alt *chan_alt;
1497455a367fSHans Petter Selasky 	uint32_t format;
14983a3f90c6SAndrew Thompson 	uint16_t curidx = 0xFFFF;
14993a3f90c6SAndrew Thompson 	uint16_t lastidx = 0xFFFF;
15003a3f90c6SAndrew Thompson 	uint16_t alt_index = 0;
1501e2524b2eSHans Petter Selasky 	uint16_t audio_rev = 0;
1502e2524b2eSHans Petter Selasky 	uint16_t x;
15033a3f90c6SAndrew Thompson 	uint8_t ep_dir;
15043a3f90c6SAndrew Thompson 	uint8_t bChannels;
15053a3f90c6SAndrew Thompson 	uint8_t bBitResolution;
15063a3f90c6SAndrew Thompson 	uint8_t audio_if = 0;
150725b74dabSHans Petter Selasky 	uint8_t uma_if_class;
15083a3f90c6SAndrew Thompson 
1509a593f6b8SAndrew Thompson 	while ((desc = usb_desc_foreach(cd, desc))) {
15103a3f90c6SAndrew Thompson 
15113a3f90c6SAndrew Thompson 		if ((desc->bDescriptorType == UDESC_INTERFACE) &&
15123a3f90c6SAndrew Thompson 		    (desc->bLength >= sizeof(*id))) {
15133a3f90c6SAndrew Thompson 
15143a3f90c6SAndrew Thompson 			id = (void *)desc;
15153a3f90c6SAndrew Thompson 
15163a3f90c6SAndrew Thompson 			if (id->bInterfaceNumber != lastidx) {
15173a3f90c6SAndrew Thompson 				lastidx = id->bInterfaceNumber;
15183a3f90c6SAndrew Thompson 				curidx++;
15193a3f90c6SAndrew Thompson 				alt_index = 0;
15203a3f90c6SAndrew Thompson 
15213a3f90c6SAndrew Thompson 			} else {
15223a3f90c6SAndrew Thompson 				alt_index++;
15233a3f90c6SAndrew Thompson 			}
15243a3f90c6SAndrew Thompson 
152576b71212SHans Petter Selasky 			if ((!(sc->sc_hid.flags & UAUDIO_HID_VALID)) &&
152676b71212SHans Petter Selasky 			    (id->bInterfaceClass == UICLASS_HID) &&
152776b71212SHans Petter Selasky 			    (id->bInterfaceSubClass == 0) &&
152876b71212SHans Petter Selasky 			    (id->bInterfaceProtocol == 0) &&
152976b71212SHans Petter Selasky 			    (alt_index == 0) &&
153076b71212SHans Petter Selasky 			    usbd_get_iface(udev, curidx) != NULL) {
153176b71212SHans Petter Selasky 				DPRINTF("Found HID interface at %d\n",
153276b71212SHans Petter Selasky 				    curidx);
153376b71212SHans Petter Selasky 				sc->sc_hid.flags |= UAUDIO_HID_VALID;
153476b71212SHans Petter Selasky 				sc->sc_hid.iface_index = curidx;
153576b71212SHans Petter Selasky 			}
153676b71212SHans Petter Selasky 
153725b74dabSHans Petter Selasky 			uma_if_class =
153825b74dabSHans Petter Selasky 			    ((id->bInterfaceClass == UICLASS_AUDIO) ||
153925b74dabSHans Petter Selasky 			    ((id->bInterfaceClass == UICLASS_VENDOR) &&
154025b74dabSHans Petter Selasky 			    (sc->sc_uq_au_vendor_class != 0)));
154125b74dabSHans Petter Selasky 
154225b74dabSHans Petter Selasky 			if ((uma_if_class != 0) && (id->bInterfaceSubClass == UISUBCLASS_AUDIOSTREAM)) {
15433a3f90c6SAndrew Thompson 				audio_if = 1;
15443a3f90c6SAndrew Thompson 			} else {
15453a3f90c6SAndrew Thompson 				audio_if = 0;
15463a3f90c6SAndrew Thompson 			}
15473a3f90c6SAndrew Thompson 
154825b74dabSHans Petter Selasky 			if ((uma_if_class != 0) &&
15493a3f90c6SAndrew Thompson 			    (id->bInterfaceSubClass == UISUBCLASS_MIDISTREAM)) {
15503a3f90c6SAndrew Thompson 
15513a3f90c6SAndrew Thompson 				/*
15523a3f90c6SAndrew Thompson 				 * XXX could allow multiple MIDI interfaces
15533a3f90c6SAndrew Thompson 				 */
15543a3f90c6SAndrew Thompson 
15553a3f90c6SAndrew Thompson 				if ((sc->sc_midi_chan.valid == 0) &&
1556a593f6b8SAndrew Thompson 				    usbd_get_iface(udev, curidx)) {
15573a3f90c6SAndrew Thompson 					sc->sc_midi_chan.iface_index = curidx;
15583a3f90c6SAndrew Thompson 					sc->sc_midi_chan.iface_alt_index = alt_index;
15593a3f90c6SAndrew Thompson 					sc->sc_midi_chan.valid = 1;
15603a3f90c6SAndrew Thompson 				}
15613a3f90c6SAndrew Thompson 			}
1562e2524b2eSHans Petter Selasky 			asid.v1 = NULL;
1563e2524b2eSHans Petter Selasky 			asf1d.v1 = NULL;
15643a3f90c6SAndrew Thompson 			ed1 = NULL;
1565e2524b2eSHans Petter Selasky 			sed.v1 = NULL;
15663a3f90c6SAndrew Thompson 		}
1567e2524b2eSHans Petter Selasky 
15681234097eSHans Petter Selasky 		if (audio_if == 0) {
1569e2524b2eSHans Petter Selasky 			if ((acdp == NULL) &&
1570e2524b2eSHans Petter Selasky 			    (desc->bDescriptorType == UDESC_CS_INTERFACE) &&
1571e2524b2eSHans Petter Selasky 			    (desc->bDescriptorSubtype == UDESCSUB_AC_HEADER) &&
1572e2524b2eSHans Petter Selasky 			    (desc->bLength >= sizeof(*acdp))) {
1573e2524b2eSHans Petter Selasky 				acdp = (void *)desc;
1574e2524b2eSHans Petter Selasky 				audio_rev = UGETW(acdp->bcdADC);
1575e2524b2eSHans Petter Selasky 			}
1576e2524b2eSHans Petter Selasky 
15771234097eSHans Petter Selasky 			/*
15781234097eSHans Petter Selasky 			 * Don't collect any USB audio descriptors if
15791234097eSHans Petter Selasky 			 * this is not an USB audio stream interface.
15801234097eSHans Petter Selasky 			 */
15811234097eSHans Petter Selasky 			continue;
15821234097eSHans Petter Selasky 		}
15831234097eSHans Petter Selasky 
1584e2524b2eSHans Petter Selasky 		if ((acdp != NULL) &&
1585e2524b2eSHans Petter Selasky 		    (desc->bDescriptorType == UDESC_CS_INTERFACE) &&
1586e2524b2eSHans Petter Selasky 		    (desc->bDescriptorSubtype == AS_GENERAL) &&
1587e2524b2eSHans Petter Selasky 		    (asid.v1 == NULL)) {
1588e2524b2eSHans Petter Selasky 			if (audio_rev >= UAUDIO_VERSION_30) {
1589e2524b2eSHans Petter Selasky 				/* FALLTHROUGH */
1590e2524b2eSHans Petter Selasky 			} else if (audio_rev >= UAUDIO_VERSION_20) {
1591e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*asid.v2)) {
1592e2524b2eSHans Petter Selasky 					asid.v2 = (void *)desc;
1593e2524b2eSHans Petter Selasky 				}
1594e2524b2eSHans Petter Selasky 			} else {
1595e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*asid.v1)) {
1596e2524b2eSHans Petter Selasky 					asid.v1 = (void *)desc;
15973a3f90c6SAndrew Thompson 				}
15983a3f90c6SAndrew Thompson 			}
1599e2524b2eSHans Petter Selasky 		}
1600e2524b2eSHans Petter Selasky 		if ((acdp != NULL) &&
1601e2524b2eSHans Petter Selasky 		    (desc->bDescriptorType == UDESC_CS_INTERFACE) &&
16023a3f90c6SAndrew Thompson 		    (desc->bDescriptorSubtype == FORMAT_TYPE) &&
1603e2524b2eSHans Petter Selasky 		    (asf1d.v1 == NULL)) {
1604e2524b2eSHans Petter Selasky 			if (audio_rev >= UAUDIO_VERSION_30) {
1605e2524b2eSHans Petter Selasky 				/* FALLTHROUGH */
1606e2524b2eSHans Petter Selasky 			} else if (audio_rev >= UAUDIO_VERSION_20) {
1607e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*asf1d.v2))
1608e2524b2eSHans Petter Selasky 					asf1d.v2 = (void *)desc;
1609e2524b2eSHans Petter Selasky 			} else {
1610e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*asf1d.v1)) {
1611e2524b2eSHans Petter Selasky 					asf1d.v1 = (void *)desc;
1612e2524b2eSHans Petter Selasky 
1613e2524b2eSHans Petter Selasky 					if (asf1d.v1->bFormatType != FORMAT_TYPE_I) {
16143a3f90c6SAndrew Thompson 						DPRINTFN(11, "ignored bFormatType = %d\n",
1615e2524b2eSHans Petter Selasky 						    asf1d.v1->bFormatType);
1616e2524b2eSHans Petter Selasky 						asf1d.v1 = NULL;
16173a3f90c6SAndrew Thompson 						continue;
16183a3f90c6SAndrew Thompson 					}
1619e2524b2eSHans Petter Selasky 					if (desc->bLength < (sizeof(*asf1d.v1) +
1620e2524b2eSHans Petter Selasky 					    ((asf1d.v1->bSamFreqType == 0) ? 6 :
1621e2524b2eSHans Petter Selasky 					    (asf1d.v1->bSamFreqType * 3)))) {
1622e2524b2eSHans Petter Selasky 						DPRINTFN(11, "invalid descriptor, "
1623e2524b2eSHans Petter Selasky 						    "too short\n");
1624e2524b2eSHans Petter Selasky 						asf1d.v1 = NULL;
16253a3f90c6SAndrew Thompson 						continue;
16263a3f90c6SAndrew Thompson 					}
16273a3f90c6SAndrew Thompson 				}
16283a3f90c6SAndrew Thompson 			}
1629e2524b2eSHans Petter Selasky 		}
16303a3f90c6SAndrew Thompson 		if ((desc->bDescriptorType == UDESC_ENDPOINT) &&
1631e2524b2eSHans Petter Selasky 		    (desc->bLength >= UEP_MINSIZE) &&
1632e2524b2eSHans Petter Selasky 		    (ed1 == NULL)) {
16333a3f90c6SAndrew Thompson 			ed1 = (void *)desc;
16343a3f90c6SAndrew Thompson 			if (UE_GET_XFERTYPE(ed1->bmAttributes) != UE_ISOCHRONOUS) {
16353a3f90c6SAndrew Thompson 				ed1 = NULL;
1636e2524b2eSHans Petter Selasky 				continue;
16373a3f90c6SAndrew Thompson 			}
16383a3f90c6SAndrew Thompson 		}
1639e2524b2eSHans Petter Selasky 		if ((acdp != NULL) &&
1640e2524b2eSHans Petter Selasky 		    (desc->bDescriptorType == UDESC_CS_ENDPOINT) &&
16413a3f90c6SAndrew Thompson 		    (desc->bDescriptorSubtype == AS_GENERAL) &&
1642e2524b2eSHans Petter Selasky 		    (sed.v1 == NULL)) {
1643e2524b2eSHans Petter Selasky 			if (audio_rev >= UAUDIO_VERSION_30) {
1644e2524b2eSHans Petter Selasky 				/* FALLTHROUGH */
1645e2524b2eSHans Petter Selasky 			} else if (audio_rev >= UAUDIO_VERSION_20) {
1646e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*sed.v2))
1647e2524b2eSHans Petter Selasky 					sed.v2 = (void *)desc;
1648e2524b2eSHans Petter Selasky 			} else {
1649e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*sed.v1))
1650e2524b2eSHans Petter Selasky 					sed.v1 = (void *)desc;
16513a3f90c6SAndrew Thompson 			}
16523a3f90c6SAndrew Thompson 		}
16531234097eSHans Petter Selasky 		if (asid.v1 == NULL || asf1d.v1 == NULL ||
16541234097eSHans Petter Selasky 		    ed1 == NULL || sed.v1 == NULL) {
1655e2524b2eSHans Petter Selasky 			/* need more descriptors */
1656e2524b2eSHans Petter Selasky 			continue;
1657e2524b2eSHans Petter Selasky 		}
16583a3f90c6SAndrew Thompson 
16593a3f90c6SAndrew Thompson 		ep_dir = UE_GET_DIR(ed1->bEndpointAddress);
16603a3f90c6SAndrew Thompson 
1661e3e05e50SAndrew Thompson 		/* We ignore sync endpoint information until further. */
16623a3f90c6SAndrew Thompson 
1663e2524b2eSHans Petter Selasky 		if (audio_rev >= UAUDIO_VERSION_30) {
1664e2524b2eSHans Petter Selasky 			goto next_ep;
1665e2524b2eSHans Petter Selasky 		} else if (audio_rev >= UAUDIO_VERSION_20) {
16663a3f90c6SAndrew Thompson 
1667e2524b2eSHans Petter Selasky 			uint32_t dwFormat;
1668e2524b2eSHans Petter Selasky 			uint8_t bSubslotSize;
16693a3f90c6SAndrew Thompson 
1670e2524b2eSHans Petter Selasky 			dwFormat = UGETDW(asid.v2->bmFormats);
1671e2524b2eSHans Petter Selasky 			bChannels = asid.v2->bNrChannels;
1672e2524b2eSHans Petter Selasky 			bBitResolution = asf1d.v2->bBitResolution;
1673e2524b2eSHans Petter Selasky 			bSubslotSize = asf1d.v2->bSubslotSize;
1674e2524b2eSHans Petter Selasky 
16759b2842baSHans Petter Selasky 			/* Map 4-byte aligned 24-bit samples into 32-bit */
16769b2842baSHans Petter Selasky 			if (bBitResolution == 24 && bSubslotSize == 4)
16779b2842baSHans Petter Selasky 				bBitResolution = 32;
16789b2842baSHans Petter Selasky 
1679e2524b2eSHans Petter Selasky 			if (bBitResolution != (bSubslotSize * 8)) {
1680e2524b2eSHans Petter Selasky 				DPRINTF("Invalid bSubslotSize\n");
1681e2524b2eSHans Petter Selasky 				goto next_ep;
1682e2524b2eSHans Petter Selasky 			}
1683e2524b2eSHans Petter Selasky 
1684e2524b2eSHans Petter Selasky 			if ((bChannels != channels) ||
1685e2524b2eSHans Petter Selasky 			    (bBitResolution != bit_resolution)) {
1686e2524b2eSHans Petter Selasky 				DPRINTF("Wrong number of channels\n");
1687e2524b2eSHans Petter Selasky 				goto next_ep;
1688e2524b2eSHans Petter Selasky 			}
1689e2524b2eSHans Petter Selasky 
1690e2524b2eSHans Petter Selasky 			for (p_fmt = uaudio20_formats;
1691e2524b2eSHans Petter Selasky 			    p_fmt->wFormat != 0; p_fmt++) {
1692e2524b2eSHans Petter Selasky 				if ((p_fmt->wFormat & dwFormat) &&
1693e2524b2eSHans Petter Selasky 				    (p_fmt->bPrecision == bBitResolution))
1694e2524b2eSHans Petter Selasky 					break;
1695e2524b2eSHans Petter Selasky 			}
1696e2524b2eSHans Petter Selasky 
1697e2524b2eSHans Petter Selasky 			if (p_fmt->wFormat == 0) {
1698e2524b2eSHans Petter Selasky 				DPRINTF("Unsupported audio format\n");
1699e2524b2eSHans Petter Selasky 				goto next_ep;
1700e2524b2eSHans Petter Selasky 			}
1701e2524b2eSHans Petter Selasky 
1702e2524b2eSHans Petter Selasky 			for (x = 0; x != 256; x++) {
1703e2524b2eSHans Petter Selasky 				if (ep_dir == UE_DIR_OUT) {
1704e2524b2eSHans Petter Selasky 					if (!(sc->sc_mixer_clocks.bit_output[x / 8] &
1705e2524b2eSHans Petter Selasky 					    (1 << (x % 8)))) {
1706e2524b2eSHans Petter Selasky 						continue;
17073a3f90c6SAndrew Thompson 					}
17083a3f90c6SAndrew Thompson 				} else {
1709e2524b2eSHans Petter Selasky 					if (!(sc->sc_mixer_clocks.bit_input[x / 8] &
1710e2524b2eSHans Petter Selasky 					    (1 << (x % 8)))) {
1711e2524b2eSHans Petter Selasky 						continue;
1712e2524b2eSHans Petter Selasky 					}
1713e2524b2eSHans Petter Selasky 				}
17143a3f90c6SAndrew Thompson 
1715e2524b2eSHans Petter Selasky 				DPRINTF("Checking clock ID=%d\n", x);
1716e2524b2eSHans Petter Selasky 
1717e2524b2eSHans Petter Selasky 				if (uaudio20_check_rate(udev,
1718e2524b2eSHans Petter Selasky 				    sc->sc_mixer_iface_no, x, rate)) {
1719e2524b2eSHans Petter Selasky 					DPRINTF("Unsupported sampling "
1720e2524b2eSHans Petter Selasky 					    "rate, id=%d\n", x);
1721e2524b2eSHans Petter Selasky 					goto next_ep;
1722e2524b2eSHans Petter Selasky 				}
1723e2524b2eSHans Petter Selasky 			}
1724e2524b2eSHans Petter Selasky 		} else {
1725e2524b2eSHans Petter Selasky 			uint16_t wFormat;
1726e2524b2eSHans Petter Selasky 
1727e2524b2eSHans Petter Selasky 			wFormat = UGETW(asid.v1->wFormatTag);
1728e2524b2eSHans Petter Selasky 			bChannels = UAUDIO_MAX_CHAN(asf1d.v1->bNrChannels);
1729e2524b2eSHans Petter Selasky 			bBitResolution = asf1d.v1->bBitResolution;
1730e2524b2eSHans Petter Selasky 
1731e2524b2eSHans Petter Selasky 			if (asf1d.v1->bSamFreqType == 0) {
1732e2524b2eSHans Petter Selasky 				DPRINTFN(16, "Sample rate: %d-%dHz\n",
1733e2524b2eSHans Petter Selasky 				    UA_SAMP_LO(asf1d.v1),
1734e2524b2eSHans Petter Selasky 				    UA_SAMP_HI(asf1d.v1));
1735e2524b2eSHans Petter Selasky 
1736e2524b2eSHans Petter Selasky 				if ((rate >= UA_SAMP_LO(asf1d.v1)) &&
1737e2524b2eSHans Petter Selasky 				    (rate <= UA_SAMP_HI(asf1d.v1)))
1738e2524b2eSHans Petter Selasky 					goto found_rate;
1739e2524b2eSHans Petter Selasky 			} else {
1740e2524b2eSHans Petter Selasky 
1741e2524b2eSHans Petter Selasky 				for (x = 0; x < asf1d.v1->bSamFreqType; x++) {
17423a3f90c6SAndrew Thompson 					DPRINTFN(16, "Sample rate = %dHz\n",
1743e2524b2eSHans Petter Selasky 					    UA_GETSAMP(asf1d.v1, x));
17443a3f90c6SAndrew Thompson 
1745e2524b2eSHans Petter Selasky 					if (rate == UA_GETSAMP(asf1d.v1, x))
17463a3f90c6SAndrew Thompson 						goto found_rate;
17473a3f90c6SAndrew Thompson 				}
17483a3f90c6SAndrew Thompson 			}
1749e2524b2eSHans Petter Selasky 			goto next_ep;
17503a3f90c6SAndrew Thompson 
17513a3f90c6SAndrew Thompson 	found_rate:
1752e2524b2eSHans Petter Selasky 			for (p_fmt = uaudio10_formats;
1753e2524b2eSHans Petter Selasky 			    p_fmt->wFormat != 0; p_fmt++) {
17543a3f90c6SAndrew Thompson 				if ((p_fmt->wFormat == wFormat) &&
1755e2524b2eSHans Petter Selasky 				    (p_fmt->bPrecision == bBitResolution))
1756e2524b2eSHans Petter Selasky 					break;
17573a3f90c6SAndrew Thompson 			}
1758e2524b2eSHans Petter Selasky 			if (p_fmt->wFormat == 0) {
1759e2524b2eSHans Petter Selasky 				DPRINTF("Unsupported audio format\n");
1760e2524b2eSHans Petter Selasky 				goto next_ep;
17613a3f90c6SAndrew Thompson 			}
17623a3f90c6SAndrew Thompson 
1763e2524b2eSHans Petter Selasky 			if ((bChannels != channels) ||
1764e2524b2eSHans Petter Selasky 			    (bBitResolution != bit_resolution)) {
1765e2524b2eSHans Petter Selasky 				DPRINTF("Wrong number of channels\n");
1766e2524b2eSHans Petter Selasky 				goto next_ep;
1767e2524b2eSHans Petter Selasky 			}
1768e2524b2eSHans Petter Selasky 		}
17693a3f90c6SAndrew Thompson 
17703a3f90c6SAndrew Thompson 		chan = (ep_dir == UE_DIR_IN) ?
1771e2524b2eSHans Petter Selasky 		    &sc->sc_rec_chan : &sc->sc_play_chan;
17723a3f90c6SAndrew Thompson 
1773455a367fSHans Petter Selasky 		if (usbd_get_iface(udev, curidx) == NULL) {
1774455a367fSHans Petter Selasky 			DPRINTF("Interface is not valid\n");
1775e2524b2eSHans Petter Selasky 			goto next_ep;
1776e2524b2eSHans Petter Selasky 		}
1777455a367fSHans Petter Selasky 		if (chan->num_alt == CHAN_MAX_ALT) {
1778455a367fSHans Petter Selasky 			DPRINTF("Too many alternate settings\n");
1779455a367fSHans Petter Selasky 			goto next_ep;
1780455a367fSHans Petter Selasky 		}
1781455a367fSHans Petter Selasky 		chan->set_alt = 0;
1782455a367fSHans Petter Selasky 		chan->cur_alt = CHAN_MAX_ALT;
17833a3f90c6SAndrew Thompson 
1784455a367fSHans Petter Selasky 		chan_alt = &chan->usb_alt[chan->num_alt++];
1785455a367fSHans Petter Selasky 
1786b850ecc1SAndrew Thompson #ifdef USB_DEBUG
17873a3f90c6SAndrew Thompson 		uaudio_chan_dump_ep_desc(ed1);
17883a3f90c6SAndrew Thompson #endif
17893a3f90c6SAndrew Thompson 		DPRINTF("Sample rate = %dHz, channels = %d, "
17903a3f90c6SAndrew Thompson 		    "bits = %d, format = %s\n", rate, channels,
17913a3f90c6SAndrew Thompson 		    bit_resolution, p_fmt->description);
17923a3f90c6SAndrew Thompson 
1793455a367fSHans Petter Selasky 		chan_alt->sample_rate = rate;
1794455a367fSHans Petter Selasky 		chan_alt->p_asf1d = asf1d;
1795455a367fSHans Petter Selasky 		chan_alt->p_ed1 = ed1;
1796455a367fSHans Petter Selasky 		chan_alt->p_fmt = p_fmt;
1797455a367fSHans Petter Selasky 		chan_alt->p_sed = sed;
1798455a367fSHans Petter Selasky 		chan_alt->iface_index = curidx;
1799455a367fSHans Petter Selasky 		chan_alt->iface_alt_index = alt_index;
1800455a367fSHans Petter Selasky 
1801455a367fSHans Petter Selasky 		usbd_set_parent_iface(sc->sc_udev, curidx,
1802455a367fSHans Petter Selasky 		    sc->sc_mixer_iface_index);
18033a3f90c6SAndrew Thompson 
18043a3f90c6SAndrew Thompson 		if (ep_dir == UE_DIR_IN)
1805455a367fSHans Petter Selasky 			chan_alt->usb_cfg = uaudio_cfg_record;
18063a3f90c6SAndrew Thompson 		else
1807455a367fSHans Petter Selasky 			chan_alt->usb_cfg = uaudio_cfg_play;
18083a3f90c6SAndrew Thompson 
1809455a367fSHans Petter Selasky 		chan_alt->sample_size = (UAUDIO_MAX_CHAN(channels) *
1810e2524b2eSHans Petter Selasky 		    p_fmt->bPrecision) / 8;
1811455a367fSHans Petter Selasky 		chan_alt->channels = channels;
18123a3f90c6SAndrew Thompson 
1813f895cc0cSHans Petter Selasky 		if (ep_dir == UE_DIR_IN &&
1814f895cc0cSHans Petter Selasky 		    usbd_get_speed(udev) == USB_SPEED_FULL) {
1815f895cc0cSHans Petter Selasky 			uaudio_record_fix_fs(ed1,
1816455a367fSHans Petter Selasky 			    chan_alt->sample_size * (rate / 1000),
1817455a367fSHans Petter Selasky 			    chan_alt->sample_size * (rate / 4000));
1818f895cc0cSHans Petter Selasky 		}
1819f895cc0cSHans Petter Selasky 
1820455a367fSHans Petter Selasky 		/* setup play/record format */
1821455a367fSHans Petter Selasky 
1822455a367fSHans Petter Selasky 		format = chan_alt->p_fmt->freebsd_fmt;
1823455a367fSHans Petter Selasky 
1824455a367fSHans Petter Selasky 		switch (chan_alt->channels) {
1825455a367fSHans Petter Selasky 		case 2:
1826455a367fSHans Petter Selasky 			/* stereo */
1827455a367fSHans Petter Selasky 			format = SND_FORMAT(format, 2, 0);
1828455a367fSHans Petter Selasky 			break;
1829455a367fSHans Petter Selasky 		case 1:
1830455a367fSHans Petter Selasky 			/* mono */
1831455a367fSHans Petter Selasky 			format = SND_FORMAT(format, 1, 0);
1832455a367fSHans Petter Selasky 			break;
1833455a367fSHans Petter Selasky 		default:
1834455a367fSHans Petter Selasky 			/* surround and more */
1835455a367fSHans Petter Selasky 			format = feeder_matrix_default_format(
1836455a367fSHans Petter Selasky 			    SND_FORMAT(format, chan_alt->channels, 0));
1837455a367fSHans Petter Selasky 			break;
1838455a367fSHans Petter Selasky 		}
1839455a367fSHans Petter Selasky 
1840455a367fSHans Petter Selasky 		/* check if format is not supported */
1841455a367fSHans Petter Selasky 		if (format == 0) {
1842455a367fSHans Petter Selasky 			DPRINTF("The selected audio format is not supported\n");
1843455a367fSHans Petter Selasky 			chan->num_alt--;
1844455a367fSHans Petter Selasky 			goto next_ep;
1845455a367fSHans Petter Selasky 		}
1846ffae621eSHans Petter Selasky 		if (chan->num_alt > 1) {
1847455a367fSHans Petter Selasky 			/* we only accumulate one format at different sample rates */
1848ffae621eSHans Petter Selasky 			if (chan->pcm_format[0] != format) {
1849455a367fSHans Petter Selasky 				DPRINTF("Multiple formats is not supported\n");
1850455a367fSHans Petter Selasky 				chan->num_alt--;
1851455a367fSHans Petter Selasky 				goto next_ep;
1852455a367fSHans Petter Selasky 			}
1853ffae621eSHans Petter Selasky 			/* ignore if duplicate sample rate entry */
1854ffae621eSHans Petter Selasky 			if (rate == chan->usb_alt[chan->num_alt - 2].sample_rate) {
1855ffae621eSHans Petter Selasky 				DPRINTF("Duplicate sample rate detected\n");
1856ffae621eSHans Petter Selasky 				chan->num_alt--;
1857ffae621eSHans Petter Selasky 				goto next_ep;
1858ffae621eSHans Petter Selasky 			}
1859ffae621eSHans Petter Selasky 		}
1860455a367fSHans Petter Selasky 		chan->pcm_cap.fmtlist = chan->pcm_format;
1861455a367fSHans Petter Selasky 		chan->pcm_cap.fmtlist[0] = format;
1862455a367fSHans Petter Selasky 
1863455a367fSHans Petter Selasky 		if (rate < chan->pcm_cap.minspeed || chan->pcm_cap.minspeed == 0)
1864455a367fSHans Petter Selasky 			chan->pcm_cap.minspeed = rate;
1865455a367fSHans Petter Selasky 		if (rate > chan->pcm_cap.maxspeed || chan->pcm_cap.maxspeed == 0)
1866455a367fSHans Petter Selasky 			chan->pcm_cap.maxspeed = rate;
1867455a367fSHans Petter Selasky 
1868e2524b2eSHans Petter Selasky 		if (sc->sc_sndstat_valid != 0) {
18693a3f90c6SAndrew Thompson 			sbuf_printf(&sc->sc_sndstat, "\n\t"
1870e2524b2eSHans Petter Selasky 			    "mode %d.%d:(%s) %dch, %dbit, %s, %dHz",
18713a3f90c6SAndrew Thompson 			    curidx, alt_index,
18723a3f90c6SAndrew Thompson 			    (ep_dir == UE_DIR_IN) ? "input" : "output",
1873e2524b2eSHans Petter Selasky 				    channels, p_fmt->bPrecision,
18743a3f90c6SAndrew Thompson 				    p_fmt->description, rate);
18753a3f90c6SAndrew Thompson 		}
1876e2524b2eSHans Petter Selasky 
1877e2524b2eSHans Petter Selasky 	next_ep:
1878e2524b2eSHans Petter Selasky 		sed.v1 = NULL;
1879e2524b2eSHans Petter Selasky 		ed1 = NULL;
18803a3f90c6SAndrew Thompson 	}
18813a3f90c6SAndrew Thompson }
18823a3f90c6SAndrew Thompson 
1883afbfddd9SAndrew Thompson /* This structure defines all the supported rates. */
1884afbfddd9SAndrew Thompson 
1885455a367fSHans Petter Selasky static const uint32_t uaudio_rate_list[CHAN_MAX_ALT] = {
18862c2752d3SHans Petter Selasky 	384000,
18872c2752d3SHans Petter Selasky 	352800,
18882c2752d3SHans Petter Selasky 	192000,
18892c2752d3SHans Petter Selasky 	176400,
1890afbfddd9SAndrew Thompson 	96000,
1891455a367fSHans Petter Selasky 	88200,
1892afbfddd9SAndrew Thompson 	88000,
1893afbfddd9SAndrew Thompson 	80000,
1894afbfddd9SAndrew Thompson 	72000,
1895afbfddd9SAndrew Thompson 	64000,
1896afbfddd9SAndrew Thompson 	56000,
1897afbfddd9SAndrew Thompson 	48000,
1898afbfddd9SAndrew Thompson 	44100,
1899afbfddd9SAndrew Thompson 	40000,
1900afbfddd9SAndrew Thompson 	32000,
1901afbfddd9SAndrew Thompson 	24000,
1902afbfddd9SAndrew Thompson 	22050,
1903afbfddd9SAndrew Thompson 	16000,
1904afbfddd9SAndrew Thompson 	11025,
1905afbfddd9SAndrew Thompson 	8000,
1906afbfddd9SAndrew Thompson 	0
1907afbfddd9SAndrew Thompson };
1908afbfddd9SAndrew Thompson 
19093a3f90c6SAndrew Thompson static void
1910760bc48eSAndrew Thompson uaudio_chan_fill_info(struct uaudio_softc *sc, struct usb_device *udev)
19113a3f90c6SAndrew Thompson {
19123a3f90c6SAndrew Thompson 	uint32_t rate = uaudio_default_rate;
1913afbfddd9SAndrew Thompson 	uint8_t z;
19143a3f90c6SAndrew Thompson 	uint8_t bits = uaudio_default_bits;
19153a3f90c6SAndrew Thompson 	uint8_t y;
19163a3f90c6SAndrew Thompson 	uint8_t channels = uaudio_default_channels;
19173a3f90c6SAndrew Thompson 	uint8_t x;
19183a3f90c6SAndrew Thompson 
19193a3f90c6SAndrew Thompson 	bits -= (bits % 8);
19203a3f90c6SAndrew Thompson 	if ((bits == 0) || (bits > 32)) {
19213a3f90c6SAndrew Thompson 		/* set a valid value */
19223a3f90c6SAndrew Thompson 		bits = 32;
19233a3f90c6SAndrew Thompson 	}
1924afbfddd9SAndrew Thompson 	if (channels == 0) {
1925afbfddd9SAndrew Thompson 		switch (usbd_get_speed(udev)) {
1926afbfddd9SAndrew Thompson 		case USB_SPEED_LOW:
1927afbfddd9SAndrew Thompson 		case USB_SPEED_FULL:
1928afbfddd9SAndrew Thompson 			/*
1929afbfddd9SAndrew Thompson 			 * Due to high bandwidth usage and problems
1930afbfddd9SAndrew Thompson 			 * with HIGH-speed split transactions we
1931afbfddd9SAndrew Thompson 			 * disable surround setups on FULL-speed USB
1932afbfddd9SAndrew Thompson 			 * by default
1933afbfddd9SAndrew Thompson 			 */
19341234097eSHans Petter Selasky 			channels = 4;
1935afbfddd9SAndrew Thompson 			break;
1936afbfddd9SAndrew Thompson 		default:
1937afbfddd9SAndrew Thompson 			channels = 16;
1938afbfddd9SAndrew Thompson 			break;
1939afbfddd9SAndrew Thompson 		}
1940afbfddd9SAndrew Thompson 	} else if (channels > 16) {
1941afbfddd9SAndrew Thompson 		channels = 16;
19423a3f90c6SAndrew Thompson 	}
19433a3f90c6SAndrew Thompson 	if (sbuf_new(&sc->sc_sndstat, NULL, 4096, SBUF_AUTOEXTEND)) {
19443a3f90c6SAndrew Thompson 		sc->sc_sndstat_valid = 1;
19453a3f90c6SAndrew Thompson 	}
19463a3f90c6SAndrew Thompson 	/* try to search for a valid config */
19473a3f90c6SAndrew Thompson 
19483a3f90c6SAndrew Thompson 	for (x = channels; x; x--) {
19493a3f90c6SAndrew Thompson 		for (y = bits; y; y -= 8) {
1950afbfddd9SAndrew Thompson 
1951afbfddd9SAndrew Thompson 			/* try user defined rate, if any */
1952afbfddd9SAndrew Thompson 			if (rate != 0)
1953afbfddd9SAndrew Thompson 				uaudio_chan_fill_info_sub(sc, udev, rate, x, y);
1954afbfddd9SAndrew Thompson 
1955afbfddd9SAndrew Thompson 			/* try find a matching rate, if any */
1956455a367fSHans Petter Selasky 			for (z = 0; uaudio_rate_list[z]; z++)
1957afbfddd9SAndrew Thompson 				uaudio_chan_fill_info_sub(sc, udev, uaudio_rate_list[z], x, y);
19583a3f90c6SAndrew Thompson 		}
19593a3f90c6SAndrew Thompson 	}
1960455a367fSHans Petter Selasky 	if (sc->sc_sndstat_valid)
19613a3f90c6SAndrew Thompson 		sbuf_finish(&sc->sc_sndstat);
19623a3f90c6SAndrew Thompson }
19633a3f90c6SAndrew Thompson 
19643a3f90c6SAndrew Thompson static void
1965b4380da7SHans Petter Selasky uaudio_chan_play_sync_callback(struct usb_xfer *xfer, usb_error_t error)
1966b4380da7SHans Petter Selasky {
1967b4380da7SHans Petter Selasky 	struct uaudio_chan *ch = usbd_xfer_softc(xfer);
1968b4380da7SHans Petter Selasky 	struct usb_page_cache *pc;
1969455a367fSHans Petter Selasky 	uint64_t sample_rate = ch->usb_alt[ch->cur_alt].sample_rate;
1970b4380da7SHans Petter Selasky 	uint8_t buf[4];
1971b4380da7SHans Petter Selasky 	uint64_t temp;
1972b4380da7SHans Petter Selasky 	int len;
1973b4380da7SHans Petter Selasky 	int actlen;
1974b4380da7SHans Petter Selasky 	int nframes;
1975b4380da7SHans Petter Selasky 
1976b4380da7SHans Petter Selasky 	usbd_xfer_status(xfer, &actlen, NULL, NULL, &nframes);
1977b4380da7SHans Petter Selasky 
1978b4380da7SHans Petter Selasky 	switch (USB_GET_STATE(xfer)) {
1979b4380da7SHans Petter Selasky 	case USB_ST_TRANSFERRED:
1980b4380da7SHans Petter Selasky 
1981b4380da7SHans Petter Selasky 		DPRINTFN(6, "transferred %d bytes\n", actlen);
1982b4380da7SHans Petter Selasky 
1983b4380da7SHans Petter Selasky 		if (nframes == 0)
1984b4380da7SHans Petter Selasky 			break;
1985b4380da7SHans Petter Selasky 		len = usbd_xfer_frame_len(xfer, 0);
1986b4380da7SHans Petter Selasky 		if (len == 0)
1987b4380da7SHans Petter Selasky 			break;
1988b4380da7SHans Petter Selasky 		if (len > sizeof(buf))
1989b4380da7SHans Petter Selasky 			len = sizeof(buf);
1990b4380da7SHans Petter Selasky 
1991b4380da7SHans Petter Selasky 		memset(buf, 0, sizeof(buf));
1992b4380da7SHans Petter Selasky 
1993b4380da7SHans Petter Selasky 		pc = usbd_xfer_get_frame(xfer, 0);
1994b4380da7SHans Petter Selasky 		usbd_copy_out(pc, 0, buf, len);
1995b4380da7SHans Petter Selasky 
1996b4380da7SHans Petter Selasky 		temp = UGETDW(buf);
1997b4380da7SHans Petter Selasky 
1998b4380da7SHans Petter Selasky 		DPRINTF("Value = 0x%08x\n", (int)temp);
1999b4380da7SHans Petter Selasky 
2000b4380da7SHans Petter Selasky 		/* auto-detect SYNC format */
2001b4380da7SHans Petter Selasky 
2002b4380da7SHans Petter Selasky 		if (len == 4)
2003b4380da7SHans Petter Selasky 			temp &= 0x0fffffff;
2004b4380da7SHans Petter Selasky 
2005b4380da7SHans Petter Selasky 		/* check for no data */
2006b4380da7SHans Petter Selasky 
2007b4380da7SHans Petter Selasky 		if (temp == 0)
2008b4380da7SHans Petter Selasky 			break;
2009b4380da7SHans Petter Selasky 
2010b4380da7SHans Petter Selasky 		/* correctly scale value */
2011b4380da7SHans Petter Selasky 
2012b4380da7SHans Petter Selasky 		temp = (temp * 125ULL) - 64;
2013b4380da7SHans Petter Selasky 
2014b4380da7SHans Petter Selasky 		/* auto adjust */
2015b4380da7SHans Petter Selasky 
2016455a367fSHans Petter Selasky 		while (temp < (sample_rate - (sample_rate / 4)))
2017b4380da7SHans Petter Selasky 			temp *= 2;
2018b4380da7SHans Petter Selasky 
2019455a367fSHans Petter Selasky 		while (temp > (sample_rate + (sample_rate / 2)))
2020b4380da7SHans Petter Selasky 			temp /= 2;
2021b4380da7SHans Petter Selasky 
2022b4380da7SHans Petter Selasky 		/* compare */
2023b4380da7SHans Petter Selasky 
2024b4380da7SHans Petter Selasky 		DPRINTF("Comparing %d < %d\n",
2025455a367fSHans Petter Selasky 		    (int)temp, (int)sample_rate);
2026b4380da7SHans Petter Selasky 
2027455a367fSHans Petter Selasky 		if (temp == sample_rate)
2028b4380da7SHans Petter Selasky 			ch->last_sync_state = UAUDIO_SYNC_NONE;
2029455a367fSHans Petter Selasky 		else if (temp > sample_rate)
2030b4380da7SHans Petter Selasky 			ch->last_sync_state = UAUDIO_SYNC_MORE;
2031b4380da7SHans Petter Selasky 		else
2032b4380da7SHans Petter Selasky 			ch->last_sync_state = UAUDIO_SYNC_LESS;
2033b4380da7SHans Petter Selasky 		break;
2034b4380da7SHans Petter Selasky 
2035b4380da7SHans Petter Selasky 	case USB_ST_SETUP:
2036b4380da7SHans Petter Selasky 		usbd_xfer_set_frames(xfer, 1);
2037b4380da7SHans Petter Selasky 		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_framelen(xfer));
2038b4380da7SHans Petter Selasky 		usbd_transfer_submit(xfer);
2039b4380da7SHans Petter Selasky 		break;
2040b4380da7SHans Petter Selasky 
2041b4380da7SHans Petter Selasky 	default:			/* Error */
2042b4380da7SHans Petter Selasky 		break;
2043b4380da7SHans Petter Selasky 	}
2044b4380da7SHans Petter Selasky }
2045b4380da7SHans Petter Selasky 
2046b4380da7SHans Petter Selasky static void
2047ed6d949aSAndrew Thompson uaudio_chan_play_callback(struct usb_xfer *xfer, usb_error_t error)
20483a3f90c6SAndrew Thompson {
2049ed6d949aSAndrew Thompson 	struct uaudio_chan *ch = usbd_xfer_softc(xfer);
2050ed6d949aSAndrew Thompson 	struct usb_page_cache *pc;
2051455a367fSHans Petter Selasky 	uint32_t sample_size = ch->usb_alt[ch->cur_alt].sample_size;
2052b4380da7SHans Petter Selasky 	uint32_t mfl;
20533a3f90c6SAndrew Thompson 	uint32_t total;
20543a3f90c6SAndrew Thompson 	uint32_t blockcount;
20553a3f90c6SAndrew Thompson 	uint32_t n;
20563a3f90c6SAndrew Thompson 	uint32_t offset;
2057afbfddd9SAndrew Thompson 	int actlen;
2058afbfddd9SAndrew Thompson 	int sumlen;
2059ed6d949aSAndrew Thompson 
2060ed6d949aSAndrew Thompson 	usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
20613a3f90c6SAndrew Thompson 
2062b029f6bbSAndrew Thompson 	if (ch->end == ch->start) {
2063b029f6bbSAndrew Thompson 		DPRINTF("no buffer!\n");
2064b029f6bbSAndrew Thompson 		return;
2065b029f6bbSAndrew Thompson 	}
20663a3f90c6SAndrew Thompson 
20673a3f90c6SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
20683a3f90c6SAndrew Thompson 	case USB_ST_TRANSFERRED:
20693a3f90c6SAndrew Thompson tr_transferred:
2070ed6d949aSAndrew Thompson 		if (actlen < sumlen) {
20713a3f90c6SAndrew Thompson 			DPRINTF("short transfer, "
2072afbfddd9SAndrew Thompson 			    "%d of %d bytes\n", actlen, sumlen);
20733a3f90c6SAndrew Thompson 		}
20743a3f90c6SAndrew Thompson 		chn_intr(ch->pcm_ch);
20753a3f90c6SAndrew Thompson 
2076b4380da7SHans Petter Selasky 		/* start SYNC transfer, if any */
2077b4380da7SHans Petter Selasky 		if ((ch->last_sync_time++ & 7) == 0)
2078b4380da7SHans Petter Selasky 			usbd_transfer_start(ch->xfer[UAUDIO_NCHANBUFS]);
2079b4380da7SHans Petter Selasky 
20803a3f90c6SAndrew Thompson 	case USB_ST_SETUP:
2081b4380da7SHans Petter Selasky 		mfl = usbd_xfer_max_framelen(xfer);
2082b4380da7SHans Petter Selasky 
2083b4380da7SHans Petter Selasky 		if (ch->bytes_per_frame[1] > mfl) {
20843a3f90c6SAndrew Thompson 			DPRINTF("bytes per transfer, %d, "
20853a3f90c6SAndrew Thompson 			    "exceeds maximum, %d!\n",
2086afbfddd9SAndrew Thompson 			    ch->bytes_per_frame[1],
2087b4380da7SHans Petter Selasky 			    mfl);
20883a3f90c6SAndrew Thompson 			break;
20893a3f90c6SAndrew Thompson 		}
2090afbfddd9SAndrew Thompson 
2091afbfddd9SAndrew Thompson 		blockcount = ch->intr_frames;
2092afbfddd9SAndrew Thompson 
2093afbfddd9SAndrew Thompson 		/* setup number of frames */
2094ed6d949aSAndrew Thompson 		usbd_xfer_set_frames(xfer, blockcount);
2095afbfddd9SAndrew Thompson 
2096afbfddd9SAndrew Thompson 		/* reset total length */
2097afbfddd9SAndrew Thompson 		total = 0;
2098afbfddd9SAndrew Thompson 
2099afbfddd9SAndrew Thompson 		/* setup frame lengths */
2100afbfddd9SAndrew Thompson 		for (n = 0; n != blockcount; n++) {
2101b4380da7SHans Petter Selasky 			uint32_t frame_len;
2102b4380da7SHans Petter Selasky 
2103afbfddd9SAndrew Thompson 			ch->sample_curr += ch->sample_rem;
2104afbfddd9SAndrew Thompson 			if (ch->sample_curr >= ch->frames_per_second) {
2105afbfddd9SAndrew Thompson 				ch->sample_curr -= ch->frames_per_second;
2106b4380da7SHans Petter Selasky 				frame_len = ch->bytes_per_frame[1];
2107afbfddd9SAndrew Thompson 			} else {
2108b4380da7SHans Petter Selasky 				frame_len = ch->bytes_per_frame[0];
2109afbfddd9SAndrew Thompson 			}
2110b4380da7SHans Petter Selasky 
2111b4380da7SHans Petter Selasky 			if (n == (blockcount - 1)) {
2112b4380da7SHans Petter Selasky 				switch (ch->last_sync_state) {
2113b4380da7SHans Petter Selasky 				case UAUDIO_SYNC_MORE:
2114b4380da7SHans Petter Selasky 					DPRINTFN(6, "sending one sample more\n");
2115455a367fSHans Petter Selasky 					if ((frame_len + sample_size) <= mfl)
2116455a367fSHans Petter Selasky 						frame_len += sample_size;
2117b4380da7SHans Petter Selasky 					ch->last_sync_state = UAUDIO_SYNC_NONE;
2118b4380da7SHans Petter Selasky 					break;
2119b4380da7SHans Petter Selasky 				case UAUDIO_SYNC_LESS:
2120b4380da7SHans Petter Selasky 					DPRINTFN(6, "sending one sample less\n");
2121455a367fSHans Petter Selasky 					if (frame_len >= sample_size)
2122455a367fSHans Petter Selasky 						frame_len -= sample_size;
2123b4380da7SHans Petter Selasky 					ch->last_sync_state = UAUDIO_SYNC_NONE;
2124b4380da7SHans Petter Selasky 					break;
2125b4380da7SHans Petter Selasky 				default:
2126b4380da7SHans Petter Selasky 					break;
2127b4380da7SHans Petter Selasky 				}
2128b4380da7SHans Petter Selasky 			}
2129b4380da7SHans Petter Selasky 
2130b4380da7SHans Petter Selasky 			usbd_xfer_set_frame_len(xfer, n, frame_len);
2131b4380da7SHans Petter Selasky 			total += frame_len;
2132afbfddd9SAndrew Thompson 		}
21333a3f90c6SAndrew Thompson 
21343a3f90c6SAndrew Thompson 		DPRINTFN(6, "transfer %d bytes\n", total);
21353a3f90c6SAndrew Thompson 
21363a3f90c6SAndrew Thompson 		offset = 0;
21373a3f90c6SAndrew Thompson 
2138ed6d949aSAndrew Thompson 		pc = usbd_xfer_get_frame(xfer, 0);
21393a3f90c6SAndrew Thompson 		while (total > 0) {
21403a3f90c6SAndrew Thompson 
21413a3f90c6SAndrew Thompson 			n = (ch->end - ch->cur);
21423a3f90c6SAndrew Thompson 			if (n > total) {
21433a3f90c6SAndrew Thompson 				n = total;
21443a3f90c6SAndrew Thompson 			}
2145ed6d949aSAndrew Thompson 			usbd_copy_in(pc, offset, ch->cur, n);
21463a3f90c6SAndrew Thompson 
21473a3f90c6SAndrew Thompson 			total -= n;
21483a3f90c6SAndrew Thompson 			ch->cur += n;
21493a3f90c6SAndrew Thompson 			offset += n;
21503a3f90c6SAndrew Thompson 
21513a3f90c6SAndrew Thompson 			if (ch->cur >= ch->end) {
21523a3f90c6SAndrew Thompson 				ch->cur = ch->start;
21533a3f90c6SAndrew Thompson 			}
21543a3f90c6SAndrew Thompson 		}
21553a3f90c6SAndrew Thompson 
2156a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
21573a3f90c6SAndrew Thompson 		break;
21583a3f90c6SAndrew Thompson 
21593a3f90c6SAndrew Thompson 	default:			/* Error */
2160ed6d949aSAndrew Thompson 		if (error == USB_ERR_CANCELLED) {
21613a3f90c6SAndrew Thompson 			break;
21623a3f90c6SAndrew Thompson 		}
21633a3f90c6SAndrew Thompson 		goto tr_transferred;
21643a3f90c6SAndrew Thompson 	}
21653a3f90c6SAndrew Thompson }
21663a3f90c6SAndrew Thompson 
21673a3f90c6SAndrew Thompson static void
2168b4380da7SHans Petter Selasky uaudio_chan_record_sync_callback(struct usb_xfer *xfer, usb_error_t error)
2169b4380da7SHans Petter Selasky {
2170b4380da7SHans Petter Selasky 	/* TODO */
2171b4380da7SHans Petter Selasky }
2172b4380da7SHans Petter Selasky 
2173b4380da7SHans Petter Selasky static void
2174ed6d949aSAndrew Thompson uaudio_chan_record_callback(struct usb_xfer *xfer, usb_error_t error)
21753a3f90c6SAndrew Thompson {
2176ed6d949aSAndrew Thompson 	struct uaudio_chan *ch = usbd_xfer_softc(xfer);
2177ed6d949aSAndrew Thompson 	struct usb_page_cache *pc;
21783a3f90c6SAndrew Thompson 	uint32_t offset0;
21793a3f90c6SAndrew Thompson 	uint32_t offset1;
2180b029f6bbSAndrew Thompson 	uint32_t mfl;
21816d917491SHans Petter Selasky 	int m;
21826d917491SHans Petter Selasky 	int n;
2183ed6d949aSAndrew Thompson 	int len;
2184b029f6bbSAndrew Thompson 	int actlen;
2185b029f6bbSAndrew Thompson 	int nframes;
21866d917491SHans Petter Selasky 	int blockcount;
2187ed6d949aSAndrew Thompson 
2188ed6d949aSAndrew Thompson 	usbd_xfer_status(xfer, &actlen, NULL, NULL, &nframes);
2189b029f6bbSAndrew Thompson 	mfl = usbd_xfer_max_framelen(xfer);
21903a3f90c6SAndrew Thompson 
2191b029f6bbSAndrew Thompson 	if (ch->end == ch->start) {
2192b029f6bbSAndrew Thompson 		DPRINTF("no buffer!\n");
2193b029f6bbSAndrew Thompson 		return;
2194b029f6bbSAndrew Thompson 	}
21953a3f90c6SAndrew Thompson 
21963a3f90c6SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
21973a3f90c6SAndrew Thompson 	case USB_ST_TRANSFERRED:
2198afbfddd9SAndrew Thompson 
2199ed6d949aSAndrew Thompson 		DPRINTFN(6, "transferred %d bytes\n", actlen);
22003a3f90c6SAndrew Thompson 
22013a3f90c6SAndrew Thompson 		offset0 = 0;
2202b029f6bbSAndrew Thompson 		pc = usbd_xfer_get_frame(xfer, 0);
22033a3f90c6SAndrew Thompson 
2204ed6d949aSAndrew Thompson 		for (n = 0; n != nframes; n++) {
22053a3f90c6SAndrew Thompson 
22063a3f90c6SAndrew Thompson 			offset1 = offset0;
22078f9e0ef9SAndrew Thompson 			len = usbd_xfer_frame_len(xfer, n);
22083a3f90c6SAndrew Thompson 
2209ed6d949aSAndrew Thompson 			while (len > 0) {
22103a3f90c6SAndrew Thompson 
22113a3f90c6SAndrew Thompson 				m = (ch->end - ch->cur);
22123a3f90c6SAndrew Thompson 
22136d917491SHans Petter Selasky 				if (m > len)
2214ed6d949aSAndrew Thompson 					m = len;
22156d917491SHans Petter Selasky 
2216ed6d949aSAndrew Thompson 				usbd_copy_out(pc, offset1, ch->cur, m);
22173a3f90c6SAndrew Thompson 
2218ed6d949aSAndrew Thompson 				len -= m;
22193a3f90c6SAndrew Thompson 				offset1 += m;
22203a3f90c6SAndrew Thompson 				ch->cur += m;
22213a3f90c6SAndrew Thompson 
22223a3f90c6SAndrew Thompson 				if (ch->cur >= ch->end) {
22233a3f90c6SAndrew Thompson 					ch->cur = ch->start;
22243a3f90c6SAndrew Thompson 				}
22253a3f90c6SAndrew Thompson 			}
22263a3f90c6SAndrew Thompson 
2227b029f6bbSAndrew Thompson 			offset0 += mfl;
22283a3f90c6SAndrew Thompson 		}
22293a3f90c6SAndrew Thompson 
22303a3f90c6SAndrew Thompson 		chn_intr(ch->pcm_ch);
22313a3f90c6SAndrew Thompson 
22323a3f90c6SAndrew Thompson 	case USB_ST_SETUP:
2233b029f6bbSAndrew Thompson tr_setup:
2234afbfddd9SAndrew Thompson 		blockcount = ch->intr_frames;
2235afbfddd9SAndrew Thompson 
2236ed6d949aSAndrew Thompson 		usbd_xfer_set_frames(xfer, blockcount);
2237ed6d949aSAndrew Thompson 		for (n = 0; n < blockcount; n++) {
2238b029f6bbSAndrew Thompson 			usbd_xfer_set_frame_len(xfer, n, mfl);
22393a3f90c6SAndrew Thompson 		}
22403a3f90c6SAndrew Thompson 
2241a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
2242b029f6bbSAndrew Thompson 		break;
22433a3f90c6SAndrew Thompson 
22443a3f90c6SAndrew Thompson 	default:			/* Error */
2245ed6d949aSAndrew Thompson 		if (error == USB_ERR_CANCELLED) {
2246b029f6bbSAndrew Thompson 			break;
22473a3f90c6SAndrew Thompson 		}
2248b029f6bbSAndrew Thompson 		goto tr_setup;
22493a3f90c6SAndrew Thompson 	}
22503a3f90c6SAndrew Thompson }
22513a3f90c6SAndrew Thompson 
22523a3f90c6SAndrew Thompson void   *
22533a3f90c6SAndrew Thompson uaudio_chan_init(struct uaudio_softc *sc, struct snd_dbuf *b,
22543a3f90c6SAndrew Thompson     struct pcm_channel *c, int dir)
22553a3f90c6SAndrew Thompson {
22563a3f90c6SAndrew Thompson 	struct uaudio_chan *ch = ((dir == PCMDIR_PLAY) ?
22573a3f90c6SAndrew Thompson 	    &sc->sc_play_chan : &sc->sc_rec_chan);
22583a3f90c6SAndrew Thompson 	uint32_t buf_size;
2259455a367fSHans Petter Selasky 	uint8_t x;
22603a3f90c6SAndrew Thompson 
2261455a367fSHans Petter Selasky 	/* store mutex and PCM channel */
22627fb43570SAndrew Thompson 
22637fb43570SAndrew Thompson 	ch->pcm_ch = c;
22647fb43570SAndrew Thompson 	ch->pcm_mtx = c->lock;
22657fb43570SAndrew Thompson 
2266455a367fSHans Petter Selasky 	/* compute worst case buffer */
22673a3f90c6SAndrew Thompson 
2268455a367fSHans Petter Selasky 	buf_size = 0;
2269455a367fSHans Petter Selasky 	for (x = 0; x != ch->num_alt; x++) {
2270455a367fSHans Petter Selasky 		uint32_t temp = uaudio_get_buffer_size(ch, x);
2271455a367fSHans Petter Selasky 		if (temp > buf_size)
2272455a367fSHans Petter Selasky 			buf_size = temp;
2273afbfddd9SAndrew Thompson 	}
2274afbfddd9SAndrew Thompson 
2275455a367fSHans Petter Selasky 	/* allow double buffering */
2276b029f6bbSAndrew Thompson 	buf_size *= 2;
2277455a367fSHans Petter Selasky 
2278455a367fSHans Petter Selasky 	DPRINTF("Worst case buffer is %d bytes\n", (int)buf_size);
2279b029f6bbSAndrew Thompson 
2280b029f6bbSAndrew Thompson 	ch->buf = malloc(buf_size, M_DEVBUF, M_WAITOK | M_ZERO);
2281b029f6bbSAndrew Thompson 	if (ch->buf == NULL)
2282b029f6bbSAndrew Thompson 		goto error;
2283b029f6bbSAndrew Thompson 	if (sndbuf_setup(b, ch->buf, buf_size) != 0)
2284b029f6bbSAndrew Thompson 		goto error;
2285b029f6bbSAndrew Thompson 
2286b029f6bbSAndrew Thompson 	ch->start = ch->buf;
2287b029f6bbSAndrew Thompson 	ch->end = ch->buf + buf_size;
2288b029f6bbSAndrew Thompson 	ch->cur = ch->buf;
2289b029f6bbSAndrew Thompson 	ch->pcm_buf = b;
2290455a367fSHans Petter Selasky 	ch->max_buf = buf_size;
2291b029f6bbSAndrew Thompson 
2292b029f6bbSAndrew Thompson 	if (ch->pcm_mtx == NULL) {
2293b029f6bbSAndrew Thompson 		DPRINTF("ERROR: PCM channels does not have a mutex!\n");
2294b029f6bbSAndrew Thompson 		goto error;
2295b029f6bbSAndrew Thompson 	}
22963a3f90c6SAndrew Thompson 	return (ch);
22973a3f90c6SAndrew Thompson 
22983a3f90c6SAndrew Thompson error:
22993a3f90c6SAndrew Thompson 	uaudio_chan_free(ch);
23003a3f90c6SAndrew Thompson 	return (NULL);
23013a3f90c6SAndrew Thompson }
23023a3f90c6SAndrew Thompson 
23033a3f90c6SAndrew Thompson int
23043a3f90c6SAndrew Thompson uaudio_chan_free(struct uaudio_chan *ch)
23053a3f90c6SAndrew Thompson {
23063a3f90c6SAndrew Thompson 	if (ch->buf != NULL) {
23073a3f90c6SAndrew Thompson 		free(ch->buf, M_DEVBUF);
23083a3f90c6SAndrew Thompson 		ch->buf = NULL;
23093a3f90c6SAndrew Thompson 	}
2310b4380da7SHans Petter Selasky 	usbd_transfer_unsetup(ch->xfer, UAUDIO_NCHANBUFS + 1);
23113a3f90c6SAndrew Thompson 
2312455a367fSHans Petter Selasky 	ch->num_alt = 0;
23133a3f90c6SAndrew Thompson 
23143a3f90c6SAndrew Thompson 	return (0);
23153a3f90c6SAndrew Thompson }
23163a3f90c6SAndrew Thompson 
23173a3f90c6SAndrew Thompson int
23183a3f90c6SAndrew Thompson uaudio_chan_set_param_blocksize(struct uaudio_chan *ch, uint32_t blocksize)
23193a3f90c6SAndrew Thompson {
2320455a367fSHans Petter Selasky 	uint32_t temp = 2 * uaudio_get_buffer_size(ch, ch->set_alt);
2321455a367fSHans Petter Selasky 
2322455a367fSHans Petter Selasky 	sndbuf_setup(ch->pcm_buf, ch->buf, temp);
2323455a367fSHans Petter Selasky 
2324455a367fSHans Petter Selasky 	ch->start = ch->buf;
2325455a367fSHans Petter Selasky 	ch->end = ch->buf + temp;
2326455a367fSHans Petter Selasky 	ch->cur = ch->buf;
2327455a367fSHans Petter Selasky 
2328455a367fSHans Petter Selasky 	return (temp / 2);
23293a3f90c6SAndrew Thompson }
23303a3f90c6SAndrew Thompson 
23313a3f90c6SAndrew Thompson int
23323a3f90c6SAndrew Thompson uaudio_chan_set_param_fragments(struct uaudio_chan *ch, uint32_t blocksize,
23333a3f90c6SAndrew Thompson     uint32_t blockcount)
23343a3f90c6SAndrew Thompson {
23353a3f90c6SAndrew Thompson 	return (1);
23363a3f90c6SAndrew Thompson }
23373a3f90c6SAndrew Thompson 
23383a3f90c6SAndrew Thompson int
23393a3f90c6SAndrew Thompson uaudio_chan_set_param_speed(struct uaudio_chan *ch, uint32_t speed)
23403a3f90c6SAndrew Thompson {
2341455a367fSHans Petter Selasky 	uint8_t x;
2342455a367fSHans Petter Selasky 
2343455a367fSHans Petter Selasky 	for (x = 0; x < ch->num_alt; x++) {
2344455a367fSHans Petter Selasky 		if (ch->usb_alt[x].sample_rate < speed) {
2345455a367fSHans Petter Selasky 			/* sample rate is too low */
2346455a367fSHans Petter Selasky 			break;
23473a3f90c6SAndrew Thompson 		}
2348455a367fSHans Petter Selasky 	}
2349455a367fSHans Petter Selasky 
2350455a367fSHans Petter Selasky 	if (x != 0)
2351455a367fSHans Petter Selasky 		x--;
2352455a367fSHans Petter Selasky 
2353455a367fSHans Petter Selasky 	ch->set_alt = x;
2354455a367fSHans Petter Selasky 
2355455a367fSHans Petter Selasky 	DPRINTF("Selecting alt %d\n", (int)x);
2356455a367fSHans Petter Selasky 
2357455a367fSHans Petter Selasky 	return (ch->usb_alt[x].sample_rate);
23583a3f90c6SAndrew Thompson }
23593a3f90c6SAndrew Thompson 
23603a3f90c6SAndrew Thompson int
23613a3f90c6SAndrew Thompson uaudio_chan_getptr(struct uaudio_chan *ch)
23623a3f90c6SAndrew Thompson {
23633a3f90c6SAndrew Thompson 	return (ch->cur - ch->start);
23643a3f90c6SAndrew Thompson }
23653a3f90c6SAndrew Thompson 
23663a3f90c6SAndrew Thompson struct pcmchan_caps *
23673a3f90c6SAndrew Thompson uaudio_chan_getcaps(struct uaudio_chan *ch)
23683a3f90c6SAndrew Thompson {
23693a3f90c6SAndrew Thompson 	return (&ch->pcm_cap);
23703a3f90c6SAndrew Thompson }
23713a3f90c6SAndrew Thompson 
237290da2b28SAriff Abdullah static struct pcmchan_matrix uaudio_chan_matrix_swap_2_0 = {
237390da2b28SAriff Abdullah 	.id = SND_CHN_MATRIX_DRV,
237490da2b28SAriff Abdullah 	.channels = 2,
237590da2b28SAriff Abdullah 	.ext = 0,
237690da2b28SAriff Abdullah 	.map = {
237790da2b28SAriff Abdullah 		/* Right */
237890da2b28SAriff Abdullah 		[0] = {
237990da2b28SAriff Abdullah 			.type = SND_CHN_T_FR,
238090da2b28SAriff Abdullah 			.members =
238190da2b28SAriff Abdullah 			    SND_CHN_T_MASK_FR | SND_CHN_T_MASK_FC |
238290da2b28SAriff Abdullah 			    SND_CHN_T_MASK_LF | SND_CHN_T_MASK_BR |
238390da2b28SAriff Abdullah 			    SND_CHN_T_MASK_BC | SND_CHN_T_MASK_SR
238490da2b28SAriff Abdullah 		},
238590da2b28SAriff Abdullah 		/* Left */
238690da2b28SAriff Abdullah 		[1] = {
238790da2b28SAriff Abdullah 			.type = SND_CHN_T_FL,
238890da2b28SAriff Abdullah 			.members =
238990da2b28SAriff Abdullah 			    SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FC |
239090da2b28SAriff Abdullah 			    SND_CHN_T_MASK_LF | SND_CHN_T_MASK_BL |
239190da2b28SAriff Abdullah 			    SND_CHN_T_MASK_BC | SND_CHN_T_MASK_SL
239290da2b28SAriff Abdullah 		},
239390da2b28SAriff Abdullah 		[2] = {
239490da2b28SAriff Abdullah 			.type = SND_CHN_T_MAX,
239590da2b28SAriff Abdullah 			.members = 0
239690da2b28SAriff Abdullah 		}
239790da2b28SAriff Abdullah 	},
239890da2b28SAriff Abdullah 	.mask = SND_CHN_T_MASK_FR | SND_CHN_T_MASK_FL,
239990da2b28SAriff Abdullah 	.offset = {  1,  0, -1, -1, -1, -1, -1, -1, -1,
240090da2b28SAriff Abdullah 		    -1, -1, -1, -1, -1, -1, -1, -1, -1  }
240190da2b28SAriff Abdullah };
240290da2b28SAriff Abdullah 
240390da2b28SAriff Abdullah struct pcmchan_matrix *
240490da2b28SAriff Abdullah uaudio_chan_getmatrix(struct uaudio_chan *ch, uint32_t format)
240590da2b28SAriff Abdullah {
240690da2b28SAriff Abdullah 	struct uaudio_softc *sc;
240790da2b28SAriff Abdullah 
240890da2b28SAriff Abdullah 	sc = ch->priv_sc;
240990da2b28SAriff Abdullah 
241090da2b28SAriff Abdullah 	if (sc != NULL && sc->sc_uq_audio_swap_lr != 0 &&
241190da2b28SAriff Abdullah 	    AFMT_CHANNEL(format) == 2)
241290da2b28SAriff Abdullah 		return (&uaudio_chan_matrix_swap_2_0);
241390da2b28SAriff Abdullah 
241490da2b28SAriff Abdullah 	return (feeder_matrix_format_map(format));
241590da2b28SAriff Abdullah }
241690da2b28SAriff Abdullah 
24173a3f90c6SAndrew Thompson int
24183a3f90c6SAndrew Thompson uaudio_chan_set_param_format(struct uaudio_chan *ch, uint32_t format)
24193a3f90c6SAndrew Thompson {
2420455a367fSHans Petter Selasky 	DPRINTF("Selecting format 0x%08x\n", (unsigned int)format);
24213a3f90c6SAndrew Thompson 	return (0);
24223a3f90c6SAndrew Thompson }
24233a3f90c6SAndrew Thompson 
24243a3f90c6SAndrew Thompson int
24253a3f90c6SAndrew Thompson uaudio_chan_start(struct uaudio_chan *ch)
24263a3f90c6SAndrew Thompson {
2427455a367fSHans Petter Selasky 	struct uaudio_softc *sc = ch->priv_sc;
2428455a367fSHans Petter Selasky 	int do_start = 0;
24293a3f90c6SAndrew Thompson 
2430455a367fSHans Petter Selasky 	usb_proc_explore_lock(sc->sc_udev);
2431455a367fSHans Petter Selasky 	if (ch->operation != CHAN_OP_DRAIN) {
2432455a367fSHans Petter Selasky 		if (ch->cur_alt == ch->set_alt &&
2433455a367fSHans Petter Selasky 		    ch->operation == CHAN_OP_NONE) {
2434455a367fSHans Petter Selasky 			/* save doing the explore task */
2435455a367fSHans Petter Selasky 			do_start = 1;
2436455a367fSHans Petter Selasky 		} else {
2437455a367fSHans Petter Selasky 			ch->operation = CHAN_OP_START;
2438455a367fSHans Petter Selasky 			(void)usb_proc_explore_msignal(sc->sc_udev,
2439455a367fSHans Petter Selasky 			    &sc->sc_config_msg[0], &sc->sc_config_msg[1]);
2440455a367fSHans Petter Selasky 		}
2441455a367fSHans Petter Selasky 	}
2442455a367fSHans Petter Selasky 	usb_proc_explore_unlock(sc->sc_udev);
2443455a367fSHans Petter Selasky 
2444455a367fSHans Petter Selasky 	if (do_start) {
2445a593f6b8SAndrew Thompson 		usbd_transfer_start(ch->xfer[0]);
2446a593f6b8SAndrew Thompson 		usbd_transfer_start(ch->xfer[1]);
2447455a367fSHans Petter Selasky 	}
24483a3f90c6SAndrew Thompson 	return (0);
24493a3f90c6SAndrew Thompson }
24503a3f90c6SAndrew Thompson 
24513a3f90c6SAndrew Thompson int
24523a3f90c6SAndrew Thompson uaudio_chan_stop(struct uaudio_chan *ch)
24533a3f90c6SAndrew Thompson {
2454455a367fSHans Petter Selasky 	struct uaudio_softc *sc = ch->priv_sc;
2455455a367fSHans Petter Selasky 	int do_stop = 0;
2456455a367fSHans Petter Selasky 
2457455a367fSHans Petter Selasky 	usb_proc_explore_lock(sc->sc_udev);
2458455a367fSHans Petter Selasky 	if (ch->operation != CHAN_OP_DRAIN) {
2459455a367fSHans Petter Selasky 		if (ch->cur_alt == ch->set_alt &&
2460455a367fSHans Petter Selasky 		    ch->operation == CHAN_OP_NONE) {
2461455a367fSHans Petter Selasky 			/* save doing the explore task */
2462455a367fSHans Petter Selasky 			do_stop = 1;
2463455a367fSHans Petter Selasky 		} else {
2464455a367fSHans Petter Selasky 			ch->operation = CHAN_OP_STOP;
2465455a367fSHans Petter Selasky 			(void)usb_proc_explore_msignal(sc->sc_udev,
2466455a367fSHans Petter Selasky 			    &sc->sc_config_msg[0], &sc->sc_config_msg[1]);
2467455a367fSHans Petter Selasky 		}
2468455a367fSHans Petter Selasky 	}
2469455a367fSHans Petter Selasky 	usb_proc_explore_unlock(sc->sc_udev);
2470455a367fSHans Petter Selasky 
2471455a367fSHans Petter Selasky 	if (do_stop) {
2472a593f6b8SAndrew Thompson 		usbd_transfer_stop(ch->xfer[0]);
2473a593f6b8SAndrew Thompson 		usbd_transfer_stop(ch->xfer[1]);
2474455a367fSHans Petter Selasky 	}
24753a3f90c6SAndrew Thompson 	return (0);
24763a3f90c6SAndrew Thompson }
24773a3f90c6SAndrew Thompson 
24783a3f90c6SAndrew Thompson /*========================================================================*
24793a3f90c6SAndrew Thompson  * AC - Audio Controller - routines
24803a3f90c6SAndrew Thompson  *========================================================================*/
24813a3f90c6SAndrew Thompson 
2482902514f6SHans Petter Selasky static int
2483902514f6SHans Petter Selasky uaudio_mixer_sysctl_handler(SYSCTL_HANDLER_ARGS)
2484902514f6SHans Petter Selasky {
2485902514f6SHans Petter Selasky 	struct uaudio_softc *sc;
2486902514f6SHans Petter Selasky 	struct uaudio_mixer_node *pmc;
2487902514f6SHans Petter Selasky 	int hint;
2488902514f6SHans Petter Selasky 	int error;
2489902514f6SHans Petter Selasky 	int temp = 0;
2490902514f6SHans Petter Selasky 	int chan = 0;
2491902514f6SHans Petter Selasky 
2492902514f6SHans Petter Selasky 	sc = (struct uaudio_softc *)oidp->oid_arg1;
2493902514f6SHans Petter Selasky 	hint = oidp->oid_arg2;
2494902514f6SHans Petter Selasky 
2495902514f6SHans Petter Selasky 	if (sc->sc_mixer_lock == NULL)
2496902514f6SHans Petter Selasky 		return (ENXIO);
2497902514f6SHans Petter Selasky 
2498902514f6SHans Petter Selasky 	/* lookup mixer node */
2499902514f6SHans Petter Selasky 
2500902514f6SHans Petter Selasky 	mtx_lock(sc->sc_mixer_lock);
2501902514f6SHans Petter Selasky 	for (pmc = sc->sc_mixer_root; pmc != NULL; pmc = pmc->next) {
2502902514f6SHans Petter Selasky 		for (chan = 0; chan != (int)pmc->nchan; chan++) {
2503902514f6SHans Petter Selasky 			if (pmc->wValue[chan] != -1 &&
2504902514f6SHans Petter Selasky 			    pmc->wValue[chan] == hint) {
2505902514f6SHans Petter Selasky 				temp = pmc->wData[chan];
2506902514f6SHans Petter Selasky 				goto found;
2507902514f6SHans Petter Selasky 			}
2508902514f6SHans Petter Selasky 		}
2509902514f6SHans Petter Selasky 	}
2510902514f6SHans Petter Selasky found:
2511902514f6SHans Petter Selasky 	mtx_unlock(sc->sc_mixer_lock);
2512902514f6SHans Petter Selasky 
2513902514f6SHans Petter Selasky 	error = sysctl_handle_int(oidp, &temp, 0, req);
2514902514f6SHans Petter Selasky 	if (error != 0 || req->newptr == NULL)
2515902514f6SHans Petter Selasky 		return (error);
2516902514f6SHans Petter Selasky 
2517902514f6SHans Petter Selasky 	/* update mixer value */
2518902514f6SHans Petter Selasky 
2519902514f6SHans Petter Selasky 	mtx_lock(sc->sc_mixer_lock);
2520902514f6SHans Petter Selasky 	if (pmc != NULL &&
2521902514f6SHans Petter Selasky 	    temp >= pmc->minval &&
2522902514f6SHans Petter Selasky 	    temp <= pmc->maxval) {
2523902514f6SHans Petter Selasky 
2524902514f6SHans Petter Selasky 		pmc->wData[chan] = temp;
2525902514f6SHans Petter Selasky 		pmc->update[(chan / 8)] |= (1 << (chan % 8));
2526902514f6SHans Petter Selasky 
2527902514f6SHans Petter Selasky 		/* start the transfer, if not already started */
2528902514f6SHans Petter Selasky 		usbd_transfer_start(sc->sc_mixer_xfer[0]);
2529902514f6SHans Petter Selasky 	}
2530902514f6SHans Petter Selasky 	mtx_unlock(sc->sc_mixer_lock);
2531902514f6SHans Petter Selasky 
2532902514f6SHans Petter Selasky 	return (0);
2533902514f6SHans Petter Selasky }
2534902514f6SHans Petter Selasky 
2535902514f6SHans Petter Selasky static void
2536902514f6SHans Petter Selasky uaudio_mixer_ctl_free(struct uaudio_softc *sc)
2537902514f6SHans Petter Selasky {
2538902514f6SHans Petter Selasky 	struct uaudio_mixer_node *p_mc;
2539902514f6SHans Petter Selasky 
2540902514f6SHans Petter Selasky 	while ((p_mc = sc->sc_mixer_root) != NULL) {
2541902514f6SHans Petter Selasky 		sc->sc_mixer_root = p_mc->next;
2542902514f6SHans Petter Selasky 		free(p_mc, M_USBDEV);
2543902514f6SHans Petter Selasky 	}
2544902514f6SHans Petter Selasky }
2545902514f6SHans Petter Selasky 
2546902514f6SHans Petter Selasky static void
2547902514f6SHans Petter Selasky uaudio_mixer_register_sysctl(struct uaudio_softc *sc, device_t dev)
2548902514f6SHans Petter Selasky {
2549902514f6SHans Petter Selasky 	struct uaudio_mixer_node *pmc;
2550902514f6SHans Petter Selasky 	struct sysctl_oid *mixer_tree;
2551902514f6SHans Petter Selasky 	struct sysctl_oid *control_tree;
2552902514f6SHans Petter Selasky 	char buf[32];
2553902514f6SHans Petter Selasky 	int chan;
2554902514f6SHans Petter Selasky 	int n;
2555902514f6SHans Petter Selasky 
2556ff4d5953SHans Petter Selasky 	mixer_tree = SYSCTL_ADD_NODE(device_get_sysctl_ctx(dev),
2557902514f6SHans Petter Selasky 	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "mixer",
2558902514f6SHans Petter Selasky 	    CTLFLAG_RD, NULL, "");
2559902514f6SHans Petter Selasky 
2560902514f6SHans Petter Selasky 	if (mixer_tree == NULL)
2561902514f6SHans Petter Selasky 		return;
2562902514f6SHans Petter Selasky 
2563902514f6SHans Petter Selasky 	for (n = 0, pmc = sc->sc_mixer_root; pmc != NULL;
2564902514f6SHans Petter Selasky 	    pmc = pmc->next, n++) {
2565902514f6SHans Petter Selasky 
2566902514f6SHans Petter Selasky 		for (chan = 0; chan < pmc->nchan; chan++) {
2567902514f6SHans Petter Selasky 
2568902514f6SHans Petter Selasky 			if (pmc->nchan > 1) {
2569902514f6SHans Petter Selasky 				snprintf(buf, sizeof(buf), "%s_%d_%d",
2570902514f6SHans Petter Selasky 				    pmc->name, n, chan);
2571902514f6SHans Petter Selasky 			} else {
2572902514f6SHans Petter Selasky 				snprintf(buf, sizeof(buf), "%s_%d",
2573902514f6SHans Petter Selasky 				    pmc->name, n);
2574902514f6SHans Petter Selasky 			}
2575902514f6SHans Petter Selasky 
2576ff4d5953SHans Petter Selasky 			control_tree = SYSCTL_ADD_NODE(device_get_sysctl_ctx(dev),
2577902514f6SHans Petter Selasky 			    SYSCTL_CHILDREN(mixer_tree), OID_AUTO, buf,
2578ff4d5953SHans Petter Selasky 			    CTLFLAG_RD, NULL, "Mixer control nodes");
2579902514f6SHans Petter Selasky 
2580902514f6SHans Petter Selasky 			if (control_tree == NULL)
2581902514f6SHans Petter Selasky 				continue;
2582902514f6SHans Petter Selasky 
2583ff4d5953SHans Petter Selasky 			SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
2584902514f6SHans Petter Selasky 			    SYSCTL_CHILDREN(control_tree),
2585902514f6SHans Petter Selasky 			    OID_AUTO, "val", CTLTYPE_INT | CTLFLAG_RW, sc,
2586902514f6SHans Petter Selasky 			    pmc->wValue[chan],
2587902514f6SHans Petter Selasky 			    uaudio_mixer_sysctl_handler, "I", "Current value");
2588902514f6SHans Petter Selasky 
2589ff4d5953SHans Petter Selasky 			SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
2590902514f6SHans Petter Selasky 			    SYSCTL_CHILDREN(control_tree),
2591902514f6SHans Petter Selasky 			    OID_AUTO, "min", CTLFLAG_RD, 0, pmc->minval,
2592902514f6SHans Petter Selasky 			    "Minimum value");
2593902514f6SHans Petter Selasky 
2594ff4d5953SHans Petter Selasky 			SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
2595902514f6SHans Petter Selasky 			    SYSCTL_CHILDREN(control_tree),
2596902514f6SHans Petter Selasky 			    OID_AUTO, "max", CTLFLAG_RD, 0, pmc->maxval,
2597902514f6SHans Petter Selasky 			    "Maximum value");
2598902514f6SHans Petter Selasky 
2599ff4d5953SHans Petter Selasky 			SYSCTL_ADD_STRING(device_get_sysctl_ctx(dev),
2600902514f6SHans Petter Selasky 			    SYSCTL_CHILDREN(control_tree),
2601902514f6SHans Petter Selasky 			    OID_AUTO, "desc", CTLFLAG_RD, pmc->desc, 0,
2602902514f6SHans Petter Selasky 			    "Description");
2603902514f6SHans Petter Selasky 		}
2604902514f6SHans Petter Selasky 	}
2605902514f6SHans Petter Selasky }
2606902514f6SHans Petter Selasky 
2607ff4d5953SHans Petter Selasky /* M-Audio FastTrack Ultra Mixer Description */
2608ff4d5953SHans Petter Selasky /* Origin: Linux USB Audio driver */
2609ff4d5953SHans Petter Selasky static void
2610ff4d5953SHans Petter Selasky uaudio_mixer_controls_create_ftu(struct uaudio_softc *sc)
2611ff4d5953SHans Petter Selasky {
2612ff4d5953SHans Petter Selasky 	int chx;
2613ff4d5953SHans Petter Selasky 	int chy;
2614ff4d5953SHans Petter Selasky 
2615f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
2616f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no);
2617f7e62ad0SHans Petter Selasky 	MIX(sc).wValue[0] = MAKE_WORD(8, 0);
2618f7e62ad0SHans Petter Selasky 	MIX(sc).class = UAC_OUTPUT;
2619f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_UNSIGNED_16;
2620f7e62ad0SHans Petter Selasky 	MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
2621f7e62ad0SHans Petter Selasky 	MIX(sc).name = "effect";
2622f7e62ad0SHans Petter Selasky 	MIX(sc).minval = 0;
2623f7e62ad0SHans Petter Selasky 	MIX(sc).maxval = 7;
2624f7e62ad0SHans Petter Selasky 	MIX(sc).mul = 7;
2625f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
2626f7e62ad0SHans Petter Selasky 	MIX(sc).update[0] = 1;
2627f7e62ad0SHans Petter Selasky 	strlcpy(MIX(sc).desc, "Room1,2,3,Hall1,2,Plate,Delay,Echo", sizeof(MIX(sc).desc));
2628f7e62ad0SHans Petter Selasky 	uaudio_mixer_add_ctl_sub(sc, &MIX(sc));
2629ff4d5953SHans Petter Selasky 
2630f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
2631f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(5, sc->sc_mixer_iface_no);
2632ff4d5953SHans Petter Selasky 
2633ff4d5953SHans Petter Selasky 	for (chx = 0; chx != 8; chx++) {
2634ff4d5953SHans Petter Selasky 		for (chy = 0; chy != 8; chy++) {
2635ff4d5953SHans Petter Selasky 
2636f7e62ad0SHans Petter Selasky 			MIX(sc).wValue[0] = MAKE_WORD(chx + 1, chy + 1);
2637f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_16;
2638f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
2639f7e62ad0SHans Petter Selasky 			MIX(sc).name = "mix_rec";
2640f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = 1;
2641f7e62ad0SHans Petter Selasky 			MIX(sc).update[0] = 1;
2642f7e62ad0SHans Petter Selasky 			MIX(sc).val_default = 0;
2643f7e62ad0SHans Petter Selasky 			snprintf(MIX(sc).desc, sizeof(MIX(sc).desc),
2644ff4d5953SHans Petter Selasky 			    "AIn%d - Out%d Record Volume", chy + 1, chx + 1);
2645ff4d5953SHans Petter Selasky 
2646f7e62ad0SHans Petter Selasky 			uaudio_mixer_add_ctl(sc, &MIX(sc));
2647ff4d5953SHans Petter Selasky 
2648f7e62ad0SHans Petter Selasky 			MIX(sc).wValue[0] = MAKE_WORD(chx + 1, chy + 1 + 8);
2649f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_16;
2650f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
2651f7e62ad0SHans Petter Selasky 			MIX(sc).name = "mix_play";
2652f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = 1;
2653f7e62ad0SHans Petter Selasky 			MIX(sc).update[0] = 1;
2654f7e62ad0SHans Petter Selasky 			MIX(sc).val_default = (chx == chy) ? 2 : 0;
2655f7e62ad0SHans Petter Selasky 			snprintf(MIX(sc).desc, sizeof(MIX(sc).desc),
2656ff4d5953SHans Petter Selasky 			    "DIn%d - Out%d Playback Volume", chy + 1, chx + 1);
2657ff4d5953SHans Petter Selasky 
2658f7e62ad0SHans Petter Selasky 			uaudio_mixer_add_ctl(sc, &MIX(sc));
2659ff4d5953SHans Petter Selasky 		}
2660ff4d5953SHans Petter Selasky 	}
2661ff4d5953SHans Petter Selasky 
2662f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
2663f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no);
2664f7e62ad0SHans Petter Selasky 	MIX(sc).wValue[0] = MAKE_WORD(2, 0);
2665f7e62ad0SHans Petter Selasky 	MIX(sc).class = UAC_OUTPUT;
2666f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_SIGNED_8;
2667f7e62ad0SHans Petter Selasky 	MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
2668f7e62ad0SHans Petter Selasky 	MIX(sc).name = "effect_vol";
2669f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
2670f7e62ad0SHans Petter Selasky 	MIX(sc).update[0] = 1;
2671f7e62ad0SHans Petter Selasky 	MIX(sc).minval = 0;
2672f7e62ad0SHans Petter Selasky 	MIX(sc).maxval = 0x7f;
2673f7e62ad0SHans Petter Selasky 	MIX(sc).mul = 0x7f;
2674f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
2675f7e62ad0SHans Petter Selasky 	MIX(sc).update[0] = 1;
2676f7e62ad0SHans Petter Selasky 	strlcpy(MIX(sc).desc, "Effect Volume", sizeof(MIX(sc).desc));
2677f7e62ad0SHans Petter Selasky 	uaudio_mixer_add_ctl_sub(sc, &MIX(sc));
2678ff4d5953SHans Petter Selasky 
2679f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
2680f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no);
2681f7e62ad0SHans Petter Selasky 	MIX(sc).wValue[0] = MAKE_WORD(3, 0);
2682f7e62ad0SHans Petter Selasky 	MIX(sc).class = UAC_OUTPUT;
2683f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_SIGNED_16;
2684f7e62ad0SHans Petter Selasky 	MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
2685f7e62ad0SHans Petter Selasky 	MIX(sc).name = "effect_dur";
2686f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
2687f7e62ad0SHans Petter Selasky 	MIX(sc).update[0] = 1;
2688f7e62ad0SHans Petter Selasky 	MIX(sc).minval = 0;
2689f7e62ad0SHans Petter Selasky 	MIX(sc).maxval = 0x7f00;
2690f7e62ad0SHans Petter Selasky 	MIX(sc).mul = 0x7f00;
2691f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
2692f7e62ad0SHans Petter Selasky 	MIX(sc).update[0] = 1;
2693f7e62ad0SHans Petter Selasky 	strlcpy(MIX(sc).desc, "Effect Duration", sizeof(MIX(sc).desc));
2694f7e62ad0SHans Petter Selasky 	uaudio_mixer_add_ctl_sub(sc, &MIX(sc));
2695ff4d5953SHans Petter Selasky 
2696f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
2697f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no);
2698f7e62ad0SHans Petter Selasky 	MIX(sc).wValue[0] = MAKE_WORD(4, 0);
2699f7e62ad0SHans Petter Selasky 	MIX(sc).class = UAC_OUTPUT;
2700f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_SIGNED_8;
2701f7e62ad0SHans Petter Selasky 	MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
2702f7e62ad0SHans Petter Selasky 	MIX(sc).name = "effect_fb";
2703f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
2704f7e62ad0SHans Petter Selasky 	MIX(sc).update[0] = 1;
2705f7e62ad0SHans Petter Selasky 	MIX(sc).minval = 0;
2706f7e62ad0SHans Petter Selasky 	MIX(sc).maxval = 0x7f;
2707f7e62ad0SHans Petter Selasky 	MIX(sc).mul = 0x7f;
2708f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
2709f7e62ad0SHans Petter Selasky 	MIX(sc).update[0] = 1;
2710f7e62ad0SHans Petter Selasky 	strlcpy(MIX(sc).desc, "Effect Feedback Volume", sizeof(MIX(sc).desc));
2711f7e62ad0SHans Petter Selasky 	uaudio_mixer_add_ctl_sub(sc, &MIX(sc));
2712ff4d5953SHans Petter Selasky 
2713f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
2714f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(7, sc->sc_mixer_iface_no);
2715ff4d5953SHans Petter Selasky 	for (chy = 0; chy != 4; chy++) {
2716ff4d5953SHans Petter Selasky 
2717f7e62ad0SHans Petter Selasky 		MIX(sc).wValue[0] = MAKE_WORD(7, chy + 1);
2718f7e62ad0SHans Petter Selasky 		MIX(sc).type = MIX_SIGNED_16;
2719f7e62ad0SHans Petter Selasky 		MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
2720f7e62ad0SHans Petter Selasky 		MIX(sc).name = "effect_ret";
2721f7e62ad0SHans Petter Selasky 		MIX(sc).nchan = 1;
2722f7e62ad0SHans Petter Selasky 		MIX(sc).update[0] = 1;
2723f7e62ad0SHans Petter Selasky 		snprintf(MIX(sc).desc, sizeof(MIX(sc).desc),
2724ff4d5953SHans Petter Selasky 		    "Effect Return %d Volume", chy + 1);
2725ff4d5953SHans Petter Selasky 
2726f7e62ad0SHans Petter Selasky 		uaudio_mixer_add_ctl(sc, &MIX(sc));
2727ff4d5953SHans Petter Selasky 	}
2728ff4d5953SHans Petter Selasky 
2729f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
2730f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(5, sc->sc_mixer_iface_no);
2731ff4d5953SHans Petter Selasky 
2732ff4d5953SHans Petter Selasky 	for (chy = 0; chy != 8; chy++) {
2733f7e62ad0SHans Petter Selasky 		MIX(sc).wValue[0] = MAKE_WORD(9, chy + 1);
2734f7e62ad0SHans Petter Selasky 		MIX(sc).type = MIX_SIGNED_16;
2735f7e62ad0SHans Petter Selasky 		MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
2736f7e62ad0SHans Petter Selasky 		MIX(sc).name = "effect_send";
2737f7e62ad0SHans Petter Selasky 		MIX(sc).nchan = 1;
2738f7e62ad0SHans Petter Selasky 		MIX(sc).update[0] = 1;
2739f7e62ad0SHans Petter Selasky 		snprintf(MIX(sc).desc, sizeof(MIX(sc).desc),
2740ff4d5953SHans Petter Selasky 		    "Effect Send AIn%d Volume", chy + 1);
2741ff4d5953SHans Petter Selasky 
2742f7e62ad0SHans Petter Selasky 		uaudio_mixer_add_ctl(sc, &MIX(sc));
2743ff4d5953SHans Petter Selasky 
2744f7e62ad0SHans Petter Selasky 		MIX(sc).wValue[0] = MAKE_WORD(9, chy + 1);
2745f7e62ad0SHans Petter Selasky 		MIX(sc).type = MIX_SIGNED_16;
2746f7e62ad0SHans Petter Selasky 		MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
2747f7e62ad0SHans Petter Selasky 		MIX(sc).name = "effect_send";
2748f7e62ad0SHans Petter Selasky 		MIX(sc).nchan = 1;
2749f7e62ad0SHans Petter Selasky 		MIX(sc).update[0] = 1;
2750f7e62ad0SHans Petter Selasky 		snprintf(MIX(sc).desc, sizeof(MIX(sc).desc),
2751ff4d5953SHans Petter Selasky 		    "Effect Send DIn%d Volume", chy + 1 + 8);
2752ff4d5953SHans Petter Selasky 
2753f7e62ad0SHans Petter Selasky 		uaudio_mixer_add_ctl(sc, &MIX(sc));
2754ff4d5953SHans Petter Selasky 	}
2755ff4d5953SHans Petter Selasky }
2756ff4d5953SHans Petter Selasky 
2757902514f6SHans Petter Selasky static void
2758902514f6SHans Petter Selasky uaudio_mixer_reload_all(struct uaudio_softc *sc)
2759902514f6SHans Petter Selasky {
2760902514f6SHans Petter Selasky 	struct uaudio_mixer_node *pmc;
2761902514f6SHans Petter Selasky 	int chan;
2762902514f6SHans Petter Selasky 
2763902514f6SHans Petter Selasky 	if (sc->sc_mixer_lock == NULL)
2764902514f6SHans Petter Selasky 		return;
2765902514f6SHans Petter Selasky 
2766902514f6SHans Petter Selasky 	mtx_lock(sc->sc_mixer_lock);
2767902514f6SHans Petter Selasky 	for (pmc = sc->sc_mixer_root; pmc != NULL; pmc = pmc->next) {
2768ff4d5953SHans Petter Selasky 		/* use reset defaults for non-oss controlled settings */
2769ff4d5953SHans Petter Selasky 		if (pmc->ctl == SOUND_MIXER_NRDEVICES)
2770ff4d5953SHans Petter Selasky 			continue;
2771902514f6SHans Petter Selasky 		for (chan = 0; chan < pmc->nchan; chan++)
2772902514f6SHans Petter Selasky 			pmc->update[chan / 8] |= (1 << (chan % 8));
2773902514f6SHans Petter Selasky 	}
2774902514f6SHans Petter Selasky 	usbd_transfer_start(sc->sc_mixer_xfer[0]);
277576b71212SHans Petter Selasky 
277676b71212SHans Petter Selasky 	/* start HID volume keys, if any */
277776b71212SHans Petter Selasky 	usbd_transfer_start(sc->sc_hid.xfer[0]);
2778902514f6SHans Petter Selasky 	mtx_unlock(sc->sc_mixer_lock);
2779902514f6SHans Petter Selasky }
2780902514f6SHans Petter Selasky 
27813a3f90c6SAndrew Thompson static void
27823a3f90c6SAndrew Thompson uaudio_mixer_add_ctl_sub(struct uaudio_softc *sc, struct uaudio_mixer_node *mc)
27833a3f90c6SAndrew Thompson {
27843a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *p_mc_new =
27853a3f90c6SAndrew Thompson 	    malloc(sizeof(*p_mc_new), M_USBDEV, M_WAITOK);
2786ff4d5953SHans Petter Selasky 	int ch;
27873a3f90c6SAndrew Thompson 
27886f068a43SHans Petter Selasky 	if (p_mc_new != NULL) {
27896f068a43SHans Petter Selasky 		memcpy(p_mc_new, mc, sizeof(*p_mc_new));
27903a3f90c6SAndrew Thompson 		p_mc_new->next = sc->sc_mixer_root;
27913a3f90c6SAndrew Thompson 		sc->sc_mixer_root = p_mc_new;
27923a3f90c6SAndrew Thompson 		sc->sc_mixer_count++;
2793ff4d5953SHans Petter Selasky 
2794ff4d5953SHans Petter Selasky 		/* set default value for all channels */
2795ff4d5953SHans Petter Selasky 		for (ch = 0; ch < p_mc_new->nchan; ch++) {
2796ff4d5953SHans Petter Selasky 			switch (p_mc_new->val_default) {
2797ff4d5953SHans Petter Selasky 			case 1:
279858e8ac5cSHans Petter Selasky 				/* 50% */
2799ff4d5953SHans Petter Selasky 				p_mc_new->wData[ch] = (p_mc_new->maxval + p_mc_new->minval) / 2;
2800ff4d5953SHans Petter Selasky 				break;
2801ff4d5953SHans Petter Selasky 			case 2:
280258e8ac5cSHans Petter Selasky 				/* 100% */
2803ff4d5953SHans Petter Selasky 				p_mc_new->wData[ch] = p_mc_new->maxval;
2804ff4d5953SHans Petter Selasky 				break;
2805ff4d5953SHans Petter Selasky 			default:
280658e8ac5cSHans Petter Selasky 				/* 0% */
2807ff4d5953SHans Petter Selasky 				p_mc_new->wData[ch] = p_mc_new->minval;
2808ff4d5953SHans Petter Selasky 				break;
2809ff4d5953SHans Petter Selasky 			}
2810ff4d5953SHans Petter Selasky 		}
28113a3f90c6SAndrew Thompson 	} else {
28123a3f90c6SAndrew Thompson 		DPRINTF("out of memory\n");
28133a3f90c6SAndrew Thompson 	}
28143a3f90c6SAndrew Thompson }
28153a3f90c6SAndrew Thompson 
28163a3f90c6SAndrew Thompson static void
28173a3f90c6SAndrew Thompson uaudio_mixer_add_ctl(struct uaudio_softc *sc, struct uaudio_mixer_node *mc)
28183a3f90c6SAndrew Thompson {
28193a3f90c6SAndrew Thompson 	int32_t res;
28203a3f90c6SAndrew Thompson 
28213a3f90c6SAndrew Thompson 	if (mc->class < UAC_NCLASSES) {
28223a3f90c6SAndrew Thompson 		DPRINTF("adding %s.%d\n",
28233a3f90c6SAndrew Thompson 		    uac_names[mc->class], mc->ctl);
28243a3f90c6SAndrew Thompson 	} else {
28253a3f90c6SAndrew Thompson 		DPRINTF("adding %d\n", mc->ctl);
28263a3f90c6SAndrew Thompson 	}
28273a3f90c6SAndrew Thompson 
28283a3f90c6SAndrew Thompson 	if (mc->type == MIX_ON_OFF) {
28293a3f90c6SAndrew Thompson 		mc->minval = 0;
28303a3f90c6SAndrew Thompson 		mc->maxval = 1;
28313a3f90c6SAndrew Thompson 	} else if (mc->type == MIX_SELECTOR) {
28323a3f90c6SAndrew Thompson 	} else {
28333a3f90c6SAndrew Thompson 
28343a3f90c6SAndrew Thompson 		/* determine min and max values */
28353a3f90c6SAndrew Thompson 
2836e2524b2eSHans Petter Selasky 		mc->minval = uaudio_mixer_get(sc->sc_udev,
2837e2524b2eSHans Petter Selasky 		    sc->sc_audio_rev, GET_MIN, mc);
2838e2524b2eSHans Petter Selasky 		mc->maxval = uaudio_mixer_get(sc->sc_udev,
2839e2524b2eSHans Petter Selasky 		    sc->sc_audio_rev, GET_MAX, mc);
28403a3f90c6SAndrew Thompson 
2841b029f6bbSAndrew Thompson 		/* check if max and min was swapped */
28423a3f90c6SAndrew Thompson 
28433a3f90c6SAndrew Thompson 		if (mc->maxval < mc->minval) {
2844b029f6bbSAndrew Thompson 			res = mc->maxval;
28453a3f90c6SAndrew Thompson 			mc->maxval = mc->minval;
2846b029f6bbSAndrew Thompson 			mc->minval = res;
28473a3f90c6SAndrew Thompson 		}
2848b029f6bbSAndrew Thompson 
2849b029f6bbSAndrew Thompson 		/* compute value range */
2850b029f6bbSAndrew Thompson 		mc->mul = mc->maxval - mc->minval;
2851b029f6bbSAndrew Thompson 		if (mc->mul == 0)
2852b029f6bbSAndrew Thompson 			mc->mul = 1;
2853b029f6bbSAndrew Thompson 
2854b029f6bbSAndrew Thompson 		/* compute value alignment */
2855e2524b2eSHans Petter Selasky 		res = uaudio_mixer_get(sc->sc_udev,
2856e2524b2eSHans Petter Selasky 		    sc->sc_audio_rev, GET_RES, mc);
28577fb43570SAndrew Thompson 
28587fb43570SAndrew Thompson 		DPRINTF("Resolution = %d\n", (int)res);
2859b029f6bbSAndrew Thompson 	}
2860b029f6bbSAndrew Thompson 
28613a3f90c6SAndrew Thompson 	uaudio_mixer_add_ctl_sub(sc, mc);
28623a3f90c6SAndrew Thompson 
2863b850ecc1SAndrew Thompson #ifdef USB_DEBUG
28643a3f90c6SAndrew Thompson 	if (uaudio_debug > 2) {
28653a3f90c6SAndrew Thompson 		uint8_t i;
28663a3f90c6SAndrew Thompson 
28673a3f90c6SAndrew Thompson 		for (i = 0; i < mc->nchan; i++) {
28683a3f90c6SAndrew Thompson 			DPRINTF("[mix] wValue=%04x\n", mc->wValue[0]);
28693a3f90c6SAndrew Thompson 		}
28703a3f90c6SAndrew Thompson 		DPRINTF("[mix] wIndex=%04x type=%d ctl='%d' "
28713a3f90c6SAndrew Thompson 		    "min=%d max=%d\n",
28723a3f90c6SAndrew Thompson 		    mc->wIndex, mc->type, mc->ctl,
28733a3f90c6SAndrew Thompson 		    mc->minval, mc->maxval);
28743a3f90c6SAndrew Thompson 	}
28753a3f90c6SAndrew Thompson #endif
28763a3f90c6SAndrew Thompson }
28773a3f90c6SAndrew Thompson 
28783a3f90c6SAndrew Thompson static void
28793a3f90c6SAndrew Thompson uaudio_mixer_add_mixer(struct uaudio_softc *sc,
28803a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
28813a3f90c6SAndrew Thompson {
2882e2524b2eSHans Petter Selasky 	const struct usb_audio_mixer_unit_0 *d0 = iot[id].u.mu_v1;
28834c21be9bSRebecca Cran 	const struct usb_audio_mixer_unit_1 *d1;
28843a3f90c6SAndrew Thompson 
28853a3f90c6SAndrew Thompson 	uint32_t bno;			/* bit number */
28863a3f90c6SAndrew Thompson 	uint32_t p;			/* bit number accumulator */
28873a3f90c6SAndrew Thompson 	uint32_t mo;			/* matching outputs */
28883a3f90c6SAndrew Thompson 	uint32_t mc;			/* matching channels */
28893a3f90c6SAndrew Thompson 	uint32_t ichs;			/* input channels */
28903a3f90c6SAndrew Thompson 	uint32_t ochs;			/* output channels */
28913a3f90c6SAndrew Thompson 	uint32_t c;
28923a3f90c6SAndrew Thompson 	uint32_t chs;			/* channels */
28933a3f90c6SAndrew Thompson 	uint32_t i;
28943a3f90c6SAndrew Thompson 	uint32_t o;
28953a3f90c6SAndrew Thompson 
28963a3f90c6SAndrew Thompson 	DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n",
28973a3f90c6SAndrew Thompson 	    d0->bUnitId, d0->bNrInPins);
28983a3f90c6SAndrew Thompson 
28993a3f90c6SAndrew Thompson 	/* compute the number of input channels */
29003a3f90c6SAndrew Thompson 
29013a3f90c6SAndrew Thompson 	ichs = 0;
29023a3f90c6SAndrew Thompson 	for (i = 0; i < d0->bNrInPins; i++) {
2903e2524b2eSHans Petter Selasky 		ichs += uaudio_mixer_get_cluster(
2904e2524b2eSHans Petter Selasky 		    d0->baSourceId[i], iot).bNrChannels;
29053a3f90c6SAndrew Thompson 	}
29063a3f90c6SAndrew Thompson 
29073a3f90c6SAndrew Thompson 	d1 = (const void *)(d0->baSourceId + d0->bNrInPins);
29083a3f90c6SAndrew Thompson 
29093a3f90c6SAndrew Thompson 	/* and the number of output channels */
29103a3f90c6SAndrew Thompson 
29113a3f90c6SAndrew Thompson 	ochs = d1->bNrChannels;
29123a3f90c6SAndrew Thompson 
29133a3f90c6SAndrew Thompson 	DPRINTFN(3, "ichs=%d ochs=%d\n", ichs, ochs);
29143a3f90c6SAndrew Thompson 
2915f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
29163a3f90c6SAndrew Thompson 
2917f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no);
2918f7e62ad0SHans Petter Selasky 	uaudio_mixer_determine_class(&iot[id], &MIX(sc));
2919f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_SIGNED_16;
29203a3f90c6SAndrew Thompson 
2921e2524b2eSHans Petter Selasky 	if (uaudio_mixer_verify_desc(d0, ((ichs * ochs) + 7) / 8) == NULL)
29223a3f90c6SAndrew Thompson 		return;
2923e2524b2eSHans Petter Selasky 
29243a3f90c6SAndrew Thompson 	for (p = i = 0; i < d0->bNrInPins; i++) {
2925e2524b2eSHans Petter Selasky 		chs = uaudio_mixer_get_cluster(
2926e2524b2eSHans Petter Selasky 		    d0->baSourceId[i], iot).bNrChannels;
29273a3f90c6SAndrew Thompson 		mc = 0;
29283a3f90c6SAndrew Thompson 		for (c = 0; c < chs; c++) {
29293a3f90c6SAndrew Thompson 			mo = 0;
29303a3f90c6SAndrew Thompson 			for (o = 0; o < ochs; o++) {
29313a3f90c6SAndrew Thompson 				bno = ((p + c) * ochs) + o;
2932e2524b2eSHans Petter Selasky 				if (BIT_TEST(d1->bmControls, bno))
29333a3f90c6SAndrew Thompson 					mo++;
29343a3f90c6SAndrew Thompson 			}
2935e2524b2eSHans Petter Selasky 			if (mo == 1)
29363a3f90c6SAndrew Thompson 				mc++;
29373a3f90c6SAndrew Thompson 		}
29383a3f90c6SAndrew Thompson 		if ((mc == chs) && (chs <= MIX_MAX_CHAN)) {
29393a3f90c6SAndrew Thompson 
29403a3f90c6SAndrew Thompson 			/* repeat bit-scan */
29413a3f90c6SAndrew Thompson 
29423a3f90c6SAndrew Thompson 			mc = 0;
29433a3f90c6SAndrew Thompson 			for (c = 0; c < chs; c++) {
29443a3f90c6SAndrew Thompson 				for (o = 0; o < ochs; o++) {
29453a3f90c6SAndrew Thompson 					bno = ((p + c) * ochs) + o;
2946e2524b2eSHans Petter Selasky 					if (BIT_TEST(d1->bmControls, bno))
2947f7e62ad0SHans Petter Selasky 						MIX(sc).wValue[mc++] = MAKE_WORD(p + c + 1, o + 1);
29483a3f90c6SAndrew Thompson 				}
29493a3f90c6SAndrew Thompson 			}
2950f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = chs;
2951f7e62ad0SHans Petter Selasky 			uaudio_mixer_add_ctl(sc, &MIX(sc));
2952e2524b2eSHans Petter Selasky 		}
2953e2524b2eSHans Petter Selasky 		p += chs;
2954e2524b2eSHans Petter Selasky 	}
2955e2524b2eSHans Petter Selasky }
2956e2524b2eSHans Petter Selasky 
2957e2524b2eSHans Petter Selasky static void
2958e2524b2eSHans Petter Selasky uaudio20_mixer_add_mixer(struct uaudio_softc *sc,
2959e2524b2eSHans Petter Selasky     const struct uaudio_terminal_node *iot, int id)
2960e2524b2eSHans Petter Selasky {
2961e2524b2eSHans Petter Selasky 	const struct usb_audio20_mixer_unit_0 *d0 = iot[id].u.mu_v2;
2962e2524b2eSHans Petter Selasky 	const struct usb_audio20_mixer_unit_1 *d1;
2963e2524b2eSHans Petter Selasky 
2964e2524b2eSHans Petter Selasky 	uint32_t bno;			/* bit number */
2965e2524b2eSHans Petter Selasky 	uint32_t p;			/* bit number accumulator */
2966e2524b2eSHans Petter Selasky 	uint32_t mo;			/* matching outputs */
2967e2524b2eSHans Petter Selasky 	uint32_t mc;			/* matching channels */
2968e2524b2eSHans Petter Selasky 	uint32_t ichs;			/* input channels */
2969e2524b2eSHans Petter Selasky 	uint32_t ochs;			/* output channels */
2970e2524b2eSHans Petter Selasky 	uint32_t c;
2971e2524b2eSHans Petter Selasky 	uint32_t chs;			/* channels */
2972e2524b2eSHans Petter Selasky 	uint32_t i;
2973e2524b2eSHans Petter Selasky 	uint32_t o;
2974e2524b2eSHans Petter Selasky 
2975e2524b2eSHans Petter Selasky 	DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n",
2976e2524b2eSHans Petter Selasky 	    d0->bUnitId, d0->bNrInPins);
2977e2524b2eSHans Petter Selasky 
2978e2524b2eSHans Petter Selasky 	/* compute the number of input channels */
2979e2524b2eSHans Petter Selasky 
2980e2524b2eSHans Petter Selasky 	ichs = 0;
2981e2524b2eSHans Petter Selasky 	for (i = 0; i < d0->bNrInPins; i++) {
2982e2524b2eSHans Petter Selasky 		ichs += uaudio20_mixer_get_cluster(
2983e2524b2eSHans Petter Selasky 		    d0->baSourceId[i], iot).bNrChannels;
2984e2524b2eSHans Petter Selasky 	}
2985e2524b2eSHans Petter Selasky 
2986e2524b2eSHans Petter Selasky 	d1 = (const void *)(d0->baSourceId + d0->bNrInPins);
2987e2524b2eSHans Petter Selasky 
2988e2524b2eSHans Petter Selasky 	/* and the number of output channels */
2989e2524b2eSHans Petter Selasky 
2990e2524b2eSHans Petter Selasky 	ochs = d1->bNrChannels;
2991e2524b2eSHans Petter Selasky 
2992e2524b2eSHans Petter Selasky 	DPRINTFN(3, "ichs=%d ochs=%d\n", ichs, ochs);
2993e2524b2eSHans Petter Selasky 
2994f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
2995e2524b2eSHans Petter Selasky 
2996f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no);
2997f7e62ad0SHans Petter Selasky 	uaudio20_mixer_determine_class(&iot[id], &MIX(sc));
2998f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_SIGNED_16;
2999e2524b2eSHans Petter Selasky 
3000e2524b2eSHans Petter Selasky 	if (uaudio20_mixer_verify_desc(d0, ((ichs * ochs) + 7) / 8) == NULL)
3001e2524b2eSHans Petter Selasky 		return;
3002e2524b2eSHans Petter Selasky 
3003e2524b2eSHans Petter Selasky 	for (p = i = 0; i < d0->bNrInPins; i++) {
3004e2524b2eSHans Petter Selasky 		chs = uaudio20_mixer_get_cluster(
3005e2524b2eSHans Petter Selasky 		    d0->baSourceId[i], iot).bNrChannels;
3006e2524b2eSHans Petter Selasky 		mc = 0;
3007e2524b2eSHans Petter Selasky 		for (c = 0; c < chs; c++) {
3008e2524b2eSHans Petter Selasky 			mo = 0;
3009e2524b2eSHans Petter Selasky 			for (o = 0; o < ochs; o++) {
3010e2524b2eSHans Petter Selasky 				bno = ((p + c) * ochs) + o;
3011e2524b2eSHans Petter Selasky 				if (BIT_TEST(d1->bmControls, bno))
3012e2524b2eSHans Petter Selasky 					mo++;
3013e2524b2eSHans Petter Selasky 			}
3014e2524b2eSHans Petter Selasky 			if (mo == 1)
3015e2524b2eSHans Petter Selasky 				mc++;
3016e2524b2eSHans Petter Selasky 		}
3017e2524b2eSHans Petter Selasky 		if ((mc == chs) && (chs <= MIX_MAX_CHAN)) {
3018e2524b2eSHans Petter Selasky 
3019e2524b2eSHans Petter Selasky 			/* repeat bit-scan */
3020e2524b2eSHans Petter Selasky 
3021e2524b2eSHans Petter Selasky 			mc = 0;
3022e2524b2eSHans Petter Selasky 			for (c = 0; c < chs; c++) {
3023e2524b2eSHans Petter Selasky 				for (o = 0; o < ochs; o++) {
3024e2524b2eSHans Petter Selasky 					bno = ((p + c) * ochs) + o;
3025e2524b2eSHans Petter Selasky 					if (BIT_TEST(d1->bmControls, bno))
3026f7e62ad0SHans Petter Selasky 						MIX(sc).wValue[mc++] = MAKE_WORD(p + c + 1, o + 1);
3027e2524b2eSHans Petter Selasky 				}
3028e2524b2eSHans Petter Selasky 			}
3029f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = chs;
3030f7e62ad0SHans Petter Selasky 			uaudio_mixer_add_ctl(sc, &MIX(sc));
30313a3f90c6SAndrew Thompson 		}
30323a3f90c6SAndrew Thompson 		p += chs;
30333a3f90c6SAndrew Thompson 	}
30343a3f90c6SAndrew Thompson }
30353a3f90c6SAndrew Thompson 
30363a3f90c6SAndrew Thompson static void
30373a3f90c6SAndrew Thompson uaudio_mixer_add_selector(struct uaudio_softc *sc,
30383a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
30393a3f90c6SAndrew Thompson {
3040e2524b2eSHans Petter Selasky 	const struct usb_audio_selector_unit *d = iot[id].u.su_v1;
30413a3f90c6SAndrew Thompson 	uint16_t i;
30423a3f90c6SAndrew Thompson 
30433a3f90c6SAndrew Thompson 	DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n",
30443a3f90c6SAndrew Thompson 	    d->bUnitId, d->bNrInPins);
30453a3f90c6SAndrew Thompson 
3046902514f6SHans Petter Selasky 	if (d->bNrInPins == 0)
30473a3f90c6SAndrew Thompson 		return;
3048902514f6SHans Petter Selasky 
3049f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
30503a3f90c6SAndrew Thompson 
3051f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no);
3052f7e62ad0SHans Petter Selasky 	MIX(sc).wValue[0] = MAKE_WORD(0, 0);
3053f7e62ad0SHans Petter Selasky 	uaudio_mixer_determine_class(&iot[id], &MIX(sc));
3054f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
3055f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_SELECTOR;
3056f7e62ad0SHans Petter Selasky 	MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
3057f7e62ad0SHans Petter Selasky 	MIX(sc).minval = 1;
3058f7e62ad0SHans Petter Selasky 	MIX(sc).maxval = d->bNrInPins;
3059f7e62ad0SHans Petter Selasky 	MIX(sc).name = "selector";
3060902514f6SHans Petter Selasky 
3061902514f6SHans Petter Selasky 	i = d->baSourceId[d->bNrInPins];
3062902514f6SHans Petter Selasky 	if (i == 0 ||
3063902514f6SHans Petter Selasky 	    usbd_req_get_string_any(sc->sc_udev, NULL,
3064f7e62ad0SHans Petter Selasky 	    MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) {
3065f7e62ad0SHans Petter Selasky 		MIX(sc).desc[0] = 0;
3066902514f6SHans Petter Selasky 	}
30673a3f90c6SAndrew Thompson 
3068f7e62ad0SHans Petter Selasky 	if (MIX(sc).maxval > MAX_SELECTOR_INPUT_PIN) {
3069f7e62ad0SHans Petter Selasky 		MIX(sc).maxval = MAX_SELECTOR_INPUT_PIN;
30703a3f90c6SAndrew Thompson 	}
3071f7e62ad0SHans Petter Selasky 	MIX(sc).mul = (MIX(sc).maxval - MIX(sc).minval);
30723a3f90c6SAndrew Thompson 	for (i = 0; i < MAX_SELECTOR_INPUT_PIN; i++) {
3073f7e62ad0SHans Petter Selasky 		MIX(sc).slctrtype[i] = SOUND_MIXER_NRDEVICES;
30743a3f90c6SAndrew Thompson 	}
30753a3f90c6SAndrew Thompson 
3076f7e62ad0SHans Petter Selasky 	for (i = 0; i < MIX(sc).maxval; i++) {
3077f7e62ad0SHans Petter Selasky 		MIX(sc).slctrtype[i] = uaudio_mixer_feature_name(
3078f7e62ad0SHans Petter Selasky 		    &iot[d->baSourceId[i]], &MIX(sc));
3079e2524b2eSHans Petter Selasky 	}
3080e2524b2eSHans Petter Selasky 
3081f7e62ad0SHans Petter Selasky 	MIX(sc).class = 0;			/* not used */
3082e2524b2eSHans Petter Selasky 
3083f7e62ad0SHans Petter Selasky 	uaudio_mixer_add_ctl(sc, &MIX(sc));
3084e2524b2eSHans Petter Selasky }
3085e2524b2eSHans Petter Selasky 
3086e2524b2eSHans Petter Selasky static void
3087e2524b2eSHans Petter Selasky uaudio20_mixer_add_selector(struct uaudio_softc *sc,
3088e2524b2eSHans Petter Selasky     const struct uaudio_terminal_node *iot, int id)
3089e2524b2eSHans Petter Selasky {
3090e2524b2eSHans Petter Selasky 	const struct usb_audio20_selector_unit *d = iot[id].u.su_v2;
3091e2524b2eSHans Petter Selasky 	uint16_t i;
3092e2524b2eSHans Petter Selasky 
3093e2524b2eSHans Petter Selasky 	DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n",
3094e2524b2eSHans Petter Selasky 	    d->bUnitId, d->bNrInPins);
3095e2524b2eSHans Petter Selasky 
3096e2524b2eSHans Petter Selasky 	if (d->bNrInPins == 0)
3097e2524b2eSHans Petter Selasky 		return;
3098e2524b2eSHans Petter Selasky 
3099f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
3100e2524b2eSHans Petter Selasky 
3101f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no);
3102f7e62ad0SHans Petter Selasky 	MIX(sc).wValue[0] = MAKE_WORD(0, 0);
3103f7e62ad0SHans Petter Selasky 	uaudio20_mixer_determine_class(&iot[id], &MIX(sc));
3104f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
3105f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_SELECTOR;
3106f7e62ad0SHans Petter Selasky 	MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
3107f7e62ad0SHans Petter Selasky 	MIX(sc).minval = 1;
3108f7e62ad0SHans Petter Selasky 	MIX(sc).maxval = d->bNrInPins;
3109f7e62ad0SHans Petter Selasky 	MIX(sc).name = "selector";
3110902514f6SHans Petter Selasky 
3111902514f6SHans Petter Selasky 	i = d->baSourceId[d->bNrInPins];
3112902514f6SHans Petter Selasky 	if (i == 0 ||
3113902514f6SHans Petter Selasky 	    usbd_req_get_string_any(sc->sc_udev, NULL,
3114f7e62ad0SHans Petter Selasky 	    MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) {
3115f7e62ad0SHans Petter Selasky 		MIX(sc).desc[0] = 0;
3116902514f6SHans Petter Selasky 	}
3117e2524b2eSHans Petter Selasky 
3118f7e62ad0SHans Petter Selasky 	if (MIX(sc).maxval > MAX_SELECTOR_INPUT_PIN)
3119f7e62ad0SHans Petter Selasky 		MIX(sc).maxval = MAX_SELECTOR_INPUT_PIN;
3120e2524b2eSHans Petter Selasky 
3121f7e62ad0SHans Petter Selasky 	MIX(sc).mul = (MIX(sc).maxval - MIX(sc).minval);
3122e2524b2eSHans Petter Selasky 	for (i = 0; i < MAX_SELECTOR_INPUT_PIN; i++)
3123f7e62ad0SHans Petter Selasky 		MIX(sc).slctrtype[i] = SOUND_MIXER_NRDEVICES;
3124e2524b2eSHans Petter Selasky 
3125f7e62ad0SHans Petter Selasky 	for (i = 0; i < MIX(sc).maxval; i++) {
3126f7e62ad0SHans Petter Selasky 		MIX(sc).slctrtype[i] = uaudio20_mixer_feature_name(
3127f7e62ad0SHans Petter Selasky 		    &iot[d->baSourceId[i]], &MIX(sc));
31283a3f90c6SAndrew Thompson 	}
31293a3f90c6SAndrew Thompson 
3130f7e62ad0SHans Petter Selasky 	MIX(sc).class = 0;			/* not used */
31313a3f90c6SAndrew Thompson 
3132f7e62ad0SHans Petter Selasky 	uaudio_mixer_add_ctl(sc, &MIX(sc));
31333a3f90c6SAndrew Thompson }
31343a3f90c6SAndrew Thompson 
31353a3f90c6SAndrew Thompson static uint32_t
31364c21be9bSRebecca Cran uaudio_mixer_feature_get_bmaControls(const struct usb_audio_feature_unit *d,
31376d917491SHans Petter Selasky     uint8_t i)
31383a3f90c6SAndrew Thompson {
31393a3f90c6SAndrew Thompson 	uint32_t temp = 0;
31406d917491SHans Petter Selasky 	uint32_t offset = (i * d->bControlSize);
31413a3f90c6SAndrew Thompson 
31423a3f90c6SAndrew Thompson 	if (d->bControlSize > 0) {
31433a3f90c6SAndrew Thompson 		temp |= d->bmaControls[offset];
31443a3f90c6SAndrew Thompson 		if (d->bControlSize > 1) {
31453a3f90c6SAndrew Thompson 			temp |= d->bmaControls[offset + 1] << 8;
31463a3f90c6SAndrew Thompson 			if (d->bControlSize > 2) {
31473a3f90c6SAndrew Thompson 				temp |= d->bmaControls[offset + 2] << 16;
31483a3f90c6SAndrew Thompson 				if (d->bControlSize > 3) {
31493a3f90c6SAndrew Thompson 					temp |= d->bmaControls[offset + 3] << 24;
31503a3f90c6SAndrew Thompson 				}
31513a3f90c6SAndrew Thompson 			}
31523a3f90c6SAndrew Thompson 		}
31533a3f90c6SAndrew Thompson 	}
31543a3f90c6SAndrew Thompson 	return (temp);
31553a3f90c6SAndrew Thompson }
31563a3f90c6SAndrew Thompson 
31573a3f90c6SAndrew Thompson static void
31583a3f90c6SAndrew Thompson uaudio_mixer_add_feature(struct uaudio_softc *sc,
31593a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
31603a3f90c6SAndrew Thompson {
3161e2524b2eSHans Petter Selasky 	const struct usb_audio_feature_unit *d = iot[id].u.fu_v1;
31623a3f90c6SAndrew Thompson 	uint32_t fumask;
31633a3f90c6SAndrew Thompson 	uint32_t mmask;
31643a3f90c6SAndrew Thompson 	uint32_t cmask;
31653a3f90c6SAndrew Thompson 	uint16_t mixernumber;
31663a3f90c6SAndrew Thompson 	uint8_t nchan;
31673a3f90c6SAndrew Thompson 	uint8_t chan;
31683a3f90c6SAndrew Thompson 	uint8_t ctl;
31693a3f90c6SAndrew Thompson 	uint8_t i;
31703a3f90c6SAndrew Thompson 
3171902514f6SHans Petter Selasky 	if (d->bControlSize == 0)
31723a3f90c6SAndrew Thompson 		return;
3173902514f6SHans Petter Selasky 
3174f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
31753a3f90c6SAndrew Thompson 
31763a3f90c6SAndrew Thompson 	nchan = (d->bLength - 7) / d->bControlSize;
31773a3f90c6SAndrew Thompson 	mmask = uaudio_mixer_feature_get_bmaControls(d, 0);
31783a3f90c6SAndrew Thompson 	cmask = 0;
31793a3f90c6SAndrew Thompson 
3180902514f6SHans Petter Selasky 	if (nchan == 0)
31813a3f90c6SAndrew Thompson 		return;
3182902514f6SHans Petter Selasky 
31833a3f90c6SAndrew Thompson 	/* figure out what we can control */
31843a3f90c6SAndrew Thompson 
31853a3f90c6SAndrew Thompson 	for (chan = 1; chan < nchan; chan++) {
31863a3f90c6SAndrew Thompson 		DPRINTFN(10, "chan=%d mask=%x\n",
31873a3f90c6SAndrew Thompson 		    chan, uaudio_mixer_feature_get_bmaControls(d, chan));
31883a3f90c6SAndrew Thompson 
31893a3f90c6SAndrew Thompson 		cmask |= uaudio_mixer_feature_get_bmaControls(d, chan);
31903a3f90c6SAndrew Thompson 	}
31913a3f90c6SAndrew Thompson 
31923a3f90c6SAndrew Thompson 	if (nchan > MIX_MAX_CHAN) {
31933a3f90c6SAndrew Thompson 		nchan = MIX_MAX_CHAN;
31943a3f90c6SAndrew Thompson 	}
3195f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no);
31963a3f90c6SAndrew Thompson 
3197902514f6SHans Petter Selasky 	i = d->bmaControls[d->bControlSize];
3198902514f6SHans Petter Selasky 	if (i == 0 ||
3199902514f6SHans Petter Selasky 	    usbd_req_get_string_any(sc->sc_udev, NULL,
3200f7e62ad0SHans Petter Selasky 	    MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) {
3201f7e62ad0SHans Petter Selasky 		MIX(sc).desc[0] = 0;
3202902514f6SHans Petter Selasky 	}
3203902514f6SHans Petter Selasky 
32043a3f90c6SAndrew Thompson 	for (ctl = 1; ctl <= LOUDNESS_CONTROL; ctl++) {
32053a3f90c6SAndrew Thompson 
32063a3f90c6SAndrew Thompson 		fumask = FU_MASK(ctl);
32073a3f90c6SAndrew Thompson 
32083a3f90c6SAndrew Thompson 		DPRINTFN(5, "ctl=%d fumask=0x%04x\n",
32093a3f90c6SAndrew Thompson 		    ctl, fumask);
32103a3f90c6SAndrew Thompson 
32113a3f90c6SAndrew Thompson 		if (mmask & fumask) {
3212f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = 1;
3213f7e62ad0SHans Petter Selasky 			MIX(sc).wValue[0] = MAKE_WORD(ctl, 0);
32143a3f90c6SAndrew Thompson 		} else if (cmask & fumask) {
3215f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = nchan - 1;
32163a3f90c6SAndrew Thompson 			for (i = 1; i < nchan; i++) {
32173a3f90c6SAndrew Thompson 				if (uaudio_mixer_feature_get_bmaControls(d, i) & fumask)
3218f7e62ad0SHans Petter Selasky 					MIX(sc).wValue[i - 1] = MAKE_WORD(ctl, i);
32193a3f90c6SAndrew Thompson 				else
3220f7e62ad0SHans Petter Selasky 					MIX(sc).wValue[i - 1] = -1;
32213a3f90c6SAndrew Thompson 			}
32223a3f90c6SAndrew Thompson 		} else {
32233a3f90c6SAndrew Thompson 			continue;
32243a3f90c6SAndrew Thompson 		}
32253a3f90c6SAndrew Thompson 
3226f7e62ad0SHans Petter Selasky 		mixernumber = uaudio_mixer_feature_name(&iot[id], &MIX(sc));
32273a3f90c6SAndrew Thompson 
32283a3f90c6SAndrew Thompson 		switch (ctl) {
32293a3f90c6SAndrew Thompson 		case MUTE_CONTROL:
3230f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
3231f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
3232f7e62ad0SHans Petter Selasky 			MIX(sc).name = "mute";
32333a3f90c6SAndrew Thompson 			break;
32343a3f90c6SAndrew Thompson 
32353a3f90c6SAndrew Thompson 		case VOLUME_CONTROL:
3236f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_16;
3237f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = mixernumber;
3238f7e62ad0SHans Petter Selasky 			MIX(sc).name = "vol";
32393a3f90c6SAndrew Thompson 			break;
32403a3f90c6SAndrew Thompson 
32413a3f90c6SAndrew Thompson 		case BASS_CONTROL:
3242f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_8;
3243f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_BASS;
3244f7e62ad0SHans Petter Selasky 			MIX(sc).name = "bass";
32453a3f90c6SAndrew Thompson 			break;
32463a3f90c6SAndrew Thompson 
32473a3f90c6SAndrew Thompson 		case MID_CONTROL:
3248f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_8;
3249f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3250f7e62ad0SHans Petter Selasky 			MIX(sc).name = "mid";
32513a3f90c6SAndrew Thompson 			break;
32523a3f90c6SAndrew Thompson 
32533a3f90c6SAndrew Thompson 		case TREBLE_CONTROL:
3254f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_8;
3255f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_TREBLE;
3256f7e62ad0SHans Petter Selasky 			MIX(sc).name = "treble";
32573a3f90c6SAndrew Thompson 			break;
32583a3f90c6SAndrew Thompson 
32593a3f90c6SAndrew Thompson 		case GRAPHIC_EQUALIZER_CONTROL:
32603a3f90c6SAndrew Thompson 			continue;	/* XXX don't add anything */
32613a3f90c6SAndrew Thompson 
32623a3f90c6SAndrew Thompson 		case AGC_CONTROL:
3263f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
3264f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3265f7e62ad0SHans Petter Selasky 			MIX(sc).name = "agc";
32663a3f90c6SAndrew Thompson 			break;
32673a3f90c6SAndrew Thompson 
32683a3f90c6SAndrew Thompson 		case DELAY_CONTROL:
3269f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_UNSIGNED_16;
3270f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3271f7e62ad0SHans Petter Selasky 			MIX(sc).name = "delay";
32723a3f90c6SAndrew Thompson 			break;
32733a3f90c6SAndrew Thompson 
32743a3f90c6SAndrew Thompson 		case BASS_BOOST_CONTROL:
3275f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
3276f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3277f7e62ad0SHans Petter Selasky 			MIX(sc).name = "boost";
32783a3f90c6SAndrew Thompson 			break;
32793a3f90c6SAndrew Thompson 
32803a3f90c6SAndrew Thompson 		case LOUDNESS_CONTROL:
3281f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
3282f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_LOUD;	/* Is this correct ? */
3283f7e62ad0SHans Petter Selasky 			MIX(sc).name = "loudness";
32843a3f90c6SAndrew Thompson 			break;
32853a3f90c6SAndrew Thompson 
32863a3f90c6SAndrew Thompson 		default:
3287f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_UNKNOWN;
32883a3f90c6SAndrew Thompson 			break;
32893a3f90c6SAndrew Thompson 		}
32903a3f90c6SAndrew Thompson 
3291f7e62ad0SHans Petter Selasky 		if (MIX(sc).type != MIX_UNKNOWN)
3292f7e62ad0SHans Petter Selasky 			uaudio_mixer_add_ctl(sc, &MIX(sc));
32933a3f90c6SAndrew Thompson 	}
32943a3f90c6SAndrew Thompson }
3295e2524b2eSHans Petter Selasky 
3296e2524b2eSHans Petter Selasky static void
3297e2524b2eSHans Petter Selasky uaudio20_mixer_add_feature(struct uaudio_softc *sc,
3298e2524b2eSHans Petter Selasky     const struct uaudio_terminal_node *iot, int id)
3299e2524b2eSHans Petter Selasky {
3300e2524b2eSHans Petter Selasky 	const struct usb_audio20_feature_unit *d = iot[id].u.fu_v2;
3301e2524b2eSHans Petter Selasky 	uint32_t ctl;
3302e2524b2eSHans Petter Selasky 	uint32_t mmask;
3303e2524b2eSHans Petter Selasky 	uint32_t cmask;
3304e2524b2eSHans Petter Selasky 	uint16_t mixernumber;
3305e2524b2eSHans Petter Selasky 	uint8_t nchan;
3306e2524b2eSHans Petter Selasky 	uint8_t chan;
3307e2524b2eSHans Petter Selasky 	uint8_t i;
3308e2524b2eSHans Petter Selasky 	uint8_t what;
3309e2524b2eSHans Petter Selasky 
3310e2524b2eSHans Petter Selasky 	if (UGETDW(d->bmaControls[0]) == 0)
3311e2524b2eSHans Petter Selasky 		return;
3312e2524b2eSHans Petter Selasky 
3313f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
3314e2524b2eSHans Petter Selasky 
3315e2524b2eSHans Petter Selasky 	nchan = (d->bLength - 6) / 4;
3316e2524b2eSHans Petter Selasky 	mmask = UGETDW(d->bmaControls[0]);
3317e2524b2eSHans Petter Selasky 	cmask = 0;
3318e2524b2eSHans Petter Selasky 
3319e2524b2eSHans Petter Selasky 	if (nchan == 0)
3320e2524b2eSHans Petter Selasky 		return;
3321e2524b2eSHans Petter Selasky 
3322e2524b2eSHans Petter Selasky 	/* figure out what we can control */
3323e2524b2eSHans Petter Selasky 
3324e2524b2eSHans Petter Selasky 	for (chan = 1; chan < nchan; chan++)
3325e2524b2eSHans Petter Selasky 		cmask |= UGETDW(d->bmaControls[chan]);
3326e2524b2eSHans Petter Selasky 
3327e2524b2eSHans Petter Selasky 	if (nchan > MIX_MAX_CHAN)
3328e2524b2eSHans Petter Selasky 		nchan = MIX_MAX_CHAN;
3329e2524b2eSHans Petter Selasky 
3330f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no);
3331e2524b2eSHans Petter Selasky 
3332902514f6SHans Petter Selasky 	i = d->bmaControls[nchan][0];
3333902514f6SHans Petter Selasky 	if (i == 0 ||
3334902514f6SHans Petter Selasky 	    usbd_req_get_string_any(sc->sc_udev, NULL,
3335f7e62ad0SHans Petter Selasky 	    MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) {
3336f7e62ad0SHans Petter Selasky 		MIX(sc).desc[0] = 0;
3337902514f6SHans Petter Selasky 	}
3338902514f6SHans Petter Selasky 
3339e2524b2eSHans Petter Selasky 	for (ctl = 3; ctl != 0; ctl <<= 2) {
3340e2524b2eSHans Petter Selasky 
3341f7e62ad0SHans Petter Selasky 		mixernumber = uaudio20_mixer_feature_name(&iot[id], &MIX(sc));
3342e2524b2eSHans Petter Selasky 
3343e2524b2eSHans Petter Selasky 		switch (ctl) {
3344e2524b2eSHans Petter Selasky 		case (3 << 0):
3345f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
3346f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
3347f7e62ad0SHans Petter Selasky 			MIX(sc).name = "mute";
3348e2524b2eSHans Petter Selasky 			what = MUTE_CONTROL;
3349e2524b2eSHans Petter Selasky 			break;
3350e2524b2eSHans Petter Selasky 		case (3 << 2):
3351f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_16;
3352f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = mixernumber;
3353f7e62ad0SHans Petter Selasky 			MIX(sc).name = "vol";
3354e2524b2eSHans Petter Selasky 			what = VOLUME_CONTROL;
3355e2524b2eSHans Petter Selasky 			break;
3356e2524b2eSHans Petter Selasky 		case (3 << 4):
3357f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_8;
3358f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_BASS;
3359f7e62ad0SHans Petter Selasky 			MIX(sc).name = "bass";
3360e2524b2eSHans Petter Selasky 			what = BASS_CONTROL;
3361e2524b2eSHans Petter Selasky 			break;
3362e2524b2eSHans Petter Selasky 		case (3 << 6):
3363f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_8;
3364f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3365f7e62ad0SHans Petter Selasky 			MIX(sc).name = "mid";
3366e2524b2eSHans Petter Selasky 			what = MID_CONTROL;
3367e2524b2eSHans Petter Selasky 			break;
3368e2524b2eSHans Petter Selasky 		case (3 << 8):
3369f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_8;
3370f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_TREBLE;
3371f7e62ad0SHans Petter Selasky 			MIX(sc).name = "treble";
3372e2524b2eSHans Petter Selasky 			what = TREBLE_CONTROL;
3373e2524b2eSHans Petter Selasky 			break;
3374e2524b2eSHans Petter Selasky 		case (3 << 12):
3375f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
3376f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3377f7e62ad0SHans Petter Selasky 			MIX(sc).name = "agc";
3378e2524b2eSHans Petter Selasky 			what = AGC_CONTROL;
3379e2524b2eSHans Petter Selasky 			break;
3380e2524b2eSHans Petter Selasky 		case (3 << 14):
3381f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_UNSIGNED_16;
3382f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3383f7e62ad0SHans Petter Selasky 			MIX(sc).name = "delay";
3384e2524b2eSHans Petter Selasky 			what = DELAY_CONTROL;
3385e2524b2eSHans Petter Selasky 			break;
3386e2524b2eSHans Petter Selasky 		case (3 << 16):
3387f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
3388f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3389f7e62ad0SHans Petter Selasky 			MIX(sc).name = "boost";
3390e2524b2eSHans Petter Selasky 			what = BASS_BOOST_CONTROL;
3391e2524b2eSHans Petter Selasky 			break;
3392e2524b2eSHans Petter Selasky 		case (3 << 18):
3393f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
3394f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_LOUD;	/* Is this correct ? */
3395f7e62ad0SHans Petter Selasky 			MIX(sc).name = "loudness";
3396e2524b2eSHans Petter Selasky 			what = LOUDNESS_CONTROL;
3397e2524b2eSHans Petter Selasky 			break;
3398e2524b2eSHans Petter Selasky 		case (3 << 20):
3399f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_16;
3400f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = mixernumber;
3401f7e62ad0SHans Petter Selasky 			MIX(sc).name = "igain";
3402e2524b2eSHans Petter Selasky 			what = INPUT_GAIN_CONTROL;
3403e2524b2eSHans Petter Selasky 			break;
3404e2524b2eSHans Petter Selasky 		case (3 << 22):
3405f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_16;
3406f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = mixernumber;
3407f7e62ad0SHans Petter Selasky 			MIX(sc).name = "igainpad";
3408e2524b2eSHans Petter Selasky 			what = INPUT_GAIN_PAD_CONTROL;
3409e2524b2eSHans Petter Selasky 			break;
3410e2524b2eSHans Petter Selasky 		default:
3411e2524b2eSHans Petter Selasky 			continue;
3412e2524b2eSHans Petter Selasky 		}
3413e2524b2eSHans Petter Selasky 
3414e2524b2eSHans Petter Selasky 		if ((mmask & ctl) == ctl) {
3415f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = 1;
3416f7e62ad0SHans Petter Selasky 			MIX(sc).wValue[0] = MAKE_WORD(what, 0);
3417e2524b2eSHans Petter Selasky 		} else if ((cmask & ctl) == ctl) {
3418f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = nchan - 1;
3419e2524b2eSHans Petter Selasky 			for (i = 1; i < nchan; i++) {
3420e2524b2eSHans Petter Selasky 				if ((UGETDW(d->bmaControls[i]) & ctl) == ctl)
3421f7e62ad0SHans Petter Selasky 					MIX(sc).wValue[i - 1] = MAKE_WORD(what, i);
3422e2524b2eSHans Petter Selasky 				else
3423f7e62ad0SHans Petter Selasky 					MIX(sc).wValue[i - 1] = -1;
3424e2524b2eSHans Petter Selasky 			}
3425e2524b2eSHans Petter Selasky 		} else {
3426e2524b2eSHans Petter Selasky 			continue;
3427e2524b2eSHans Petter Selasky 		}
3428e2524b2eSHans Petter Selasky 
3429f7e62ad0SHans Petter Selasky 		if (MIX(sc).type != MIX_UNKNOWN)
3430f7e62ad0SHans Petter Selasky 			uaudio_mixer_add_ctl(sc, &MIX(sc));
3431e2524b2eSHans Petter Selasky 	}
34323a3f90c6SAndrew Thompson }
34333a3f90c6SAndrew Thompson 
34343a3f90c6SAndrew Thompson static void
34353a3f90c6SAndrew Thompson uaudio_mixer_add_processing_updown(struct uaudio_softc *sc,
34363a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
34373a3f90c6SAndrew Thompson {
3438e2524b2eSHans Petter Selasky 	const struct usb_audio_processing_unit_0 *d0 = iot[id].u.pu_v1;
34394c21be9bSRebecca Cran 	const struct usb_audio_processing_unit_1 *d1 =
34403a3f90c6SAndrew Thompson 	    (const void *)(d0->baSourceId + d0->bNrInPins);
34414c21be9bSRebecca Cran 	const struct usb_audio_processing_unit_updown *ud =
34423a3f90c6SAndrew Thompson 	    (const void *)(d1->bmControls + d1->bControlSize);
34433a3f90c6SAndrew Thompson 	uint8_t i;
34443a3f90c6SAndrew Thompson 
34453a3f90c6SAndrew Thompson 	if (uaudio_mixer_verify_desc(d0, sizeof(*ud)) == NULL) {
34463a3f90c6SAndrew Thompson 		return;
34473a3f90c6SAndrew Thompson 	}
34483a3f90c6SAndrew Thompson 	if (uaudio_mixer_verify_desc(d0, sizeof(*ud) + (2 * ud->bNrModes))
34493a3f90c6SAndrew Thompson 	    == NULL) {
34503a3f90c6SAndrew Thompson 		return;
34513a3f90c6SAndrew Thompson 	}
34523a3f90c6SAndrew Thompson 	DPRINTFN(3, "bUnitId=%d bNrModes=%d\n",
34533a3f90c6SAndrew Thompson 	    d0->bUnitId, ud->bNrModes);
34543a3f90c6SAndrew Thompson 
34553a3f90c6SAndrew Thompson 	if (!(d1->bmControls[0] & UA_PROC_MASK(UD_MODE_SELECT_CONTROL))) {
34563a3f90c6SAndrew Thompson 		DPRINTF("no mode select\n");
34573a3f90c6SAndrew Thompson 		return;
34583a3f90c6SAndrew Thompson 	}
3459f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
34603a3f90c6SAndrew Thompson 
3461f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no);
3462f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
3463f7e62ad0SHans Petter Selasky 	MIX(sc).wValue[0] = MAKE_WORD(UD_MODE_SELECT_CONTROL, 0);
3464f7e62ad0SHans Petter Selasky 	uaudio_mixer_determine_class(&iot[id], &MIX(sc));
3465f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_ON_OFF;		/* XXX */
34663a3f90c6SAndrew Thompson 
34673a3f90c6SAndrew Thompson 	for (i = 0; i < ud->bNrModes; i++) {
34683a3f90c6SAndrew Thompson 		DPRINTFN(3, "i=%d bm=0x%x\n", i, UGETW(ud->waModes[i]));
34693a3f90c6SAndrew Thompson 		/* XXX */
34703a3f90c6SAndrew Thompson 	}
34713a3f90c6SAndrew Thompson 
3472f7e62ad0SHans Petter Selasky 	uaudio_mixer_add_ctl(sc, &MIX(sc));
34733a3f90c6SAndrew Thompson }
34743a3f90c6SAndrew Thompson 
34753a3f90c6SAndrew Thompson static void
34763a3f90c6SAndrew Thompson uaudio_mixer_add_processing(struct uaudio_softc *sc,
34773a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
34783a3f90c6SAndrew Thompson {
3479e2524b2eSHans Petter Selasky 	const struct usb_audio_processing_unit_0 *d0 = iot[id].u.pu_v1;
34804c21be9bSRebecca Cran 	const struct usb_audio_processing_unit_1 *d1 =
34813a3f90c6SAndrew Thompson 	    (const void *)(d0->baSourceId + d0->bNrInPins);
34823a3f90c6SAndrew Thompson 	uint16_t ptype;
34833a3f90c6SAndrew Thompson 
3484f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
34853a3f90c6SAndrew Thompson 
34863a3f90c6SAndrew Thompson 	ptype = UGETW(d0->wProcessType);
34873a3f90c6SAndrew Thompson 
34883a3f90c6SAndrew Thompson 	DPRINTFN(3, "wProcessType=%d bUnitId=%d "
34893a3f90c6SAndrew Thompson 	    "bNrInPins=%d\n", ptype, d0->bUnitId, d0->bNrInPins);
34903a3f90c6SAndrew Thompson 
34913a3f90c6SAndrew Thompson 	if (d1->bControlSize == 0) {
34923a3f90c6SAndrew Thompson 		return;
34933a3f90c6SAndrew Thompson 	}
34943a3f90c6SAndrew Thompson 	if (d1->bmControls[0] & UA_PROC_ENABLE_MASK) {
3495f7e62ad0SHans Petter Selasky 		MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no);
3496f7e62ad0SHans Petter Selasky 		MIX(sc).nchan = 1;
3497f7e62ad0SHans Petter Selasky 		MIX(sc).wValue[0] = MAKE_WORD(XX_ENABLE_CONTROL, 0);
3498f7e62ad0SHans Petter Selasky 		uaudio_mixer_determine_class(&iot[id], &MIX(sc));
3499f7e62ad0SHans Petter Selasky 		MIX(sc).type = MIX_ON_OFF;
3500f7e62ad0SHans Petter Selasky 		uaudio_mixer_add_ctl(sc, &MIX(sc));
35013a3f90c6SAndrew Thompson 	}
35023a3f90c6SAndrew Thompson 	switch (ptype) {
35033a3f90c6SAndrew Thompson 	case UPDOWNMIX_PROCESS:
35043a3f90c6SAndrew Thompson 		uaudio_mixer_add_processing_updown(sc, iot, id);
35053a3f90c6SAndrew Thompson 		break;
35063a3f90c6SAndrew Thompson 
35073a3f90c6SAndrew Thompson 	case DOLBY_PROLOGIC_PROCESS:
35083a3f90c6SAndrew Thompson 	case P3D_STEREO_EXTENDER_PROCESS:
35093a3f90c6SAndrew Thompson 	case REVERBATION_PROCESS:
35103a3f90c6SAndrew Thompson 	case CHORUS_PROCESS:
35113a3f90c6SAndrew Thompson 	case DYN_RANGE_COMP_PROCESS:
35123a3f90c6SAndrew Thompson 	default:
35133a3f90c6SAndrew Thompson 		DPRINTF("unit %d, type=%d is not implemented\n",
35143a3f90c6SAndrew Thompson 		    d0->bUnitId, ptype);
35153a3f90c6SAndrew Thompson 		break;
35163a3f90c6SAndrew Thompson 	}
35173a3f90c6SAndrew Thompson }
35183a3f90c6SAndrew Thompson 
35193a3f90c6SAndrew Thompson static void
35203a3f90c6SAndrew Thompson uaudio_mixer_add_extension(struct uaudio_softc *sc,
35213a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
35223a3f90c6SAndrew Thompson {
3523e2524b2eSHans Petter Selasky 	const struct usb_audio_extension_unit_0 *d0 = iot[id].u.eu_v1;
35244c21be9bSRebecca Cran 	const struct usb_audio_extension_unit_1 *d1 =
35253a3f90c6SAndrew Thompson 	    (const void *)(d0->baSourceId + d0->bNrInPins);
35263a3f90c6SAndrew Thompson 
35273a3f90c6SAndrew Thompson 	DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n",
35283a3f90c6SAndrew Thompson 	    d0->bUnitId, d0->bNrInPins);
35293a3f90c6SAndrew Thompson 
35303a3f90c6SAndrew Thompson 	if (sc->sc_uq_au_no_xu) {
35313a3f90c6SAndrew Thompson 		return;
35323a3f90c6SAndrew Thompson 	}
35333a3f90c6SAndrew Thompson 	if (d1->bControlSize == 0) {
35343a3f90c6SAndrew Thompson 		return;
35353a3f90c6SAndrew Thompson 	}
35363a3f90c6SAndrew Thompson 	if (d1->bmControls[0] & UA_EXT_ENABLE_MASK) {
35373a3f90c6SAndrew Thompson 
3538f7e62ad0SHans Petter Selasky 		memset(&MIX(sc), 0, sizeof(MIX(sc)));
35393a3f90c6SAndrew Thompson 
3540f7e62ad0SHans Petter Selasky 		MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no);
3541f7e62ad0SHans Petter Selasky 		MIX(sc).nchan = 1;
3542f7e62ad0SHans Petter Selasky 		MIX(sc).wValue[0] = MAKE_WORD(UA_EXT_ENABLE, 0);
3543f7e62ad0SHans Petter Selasky 		uaudio_mixer_determine_class(&iot[id], &MIX(sc));
3544f7e62ad0SHans Petter Selasky 		MIX(sc).type = MIX_ON_OFF;
35453a3f90c6SAndrew Thompson 
3546f7e62ad0SHans Petter Selasky 		uaudio_mixer_add_ctl(sc, &MIX(sc));
35473a3f90c6SAndrew Thompson 	}
35483a3f90c6SAndrew Thompson }
35493a3f90c6SAndrew Thompson 
35503a3f90c6SAndrew Thompson static const void *
35513a3f90c6SAndrew Thompson uaudio_mixer_verify_desc(const void *arg, uint32_t len)
35523a3f90c6SAndrew Thompson {
35534c21be9bSRebecca Cran 	const struct usb_audio_mixer_unit_1 *d1;
35544c21be9bSRebecca Cran 	const struct usb_audio_extension_unit_1 *e1;
35554c21be9bSRebecca Cran 	const struct usb_audio_processing_unit_1 *u1;
35563a3f90c6SAndrew Thompson 
35573a3f90c6SAndrew Thompson 	union {
3558760bc48eSAndrew Thompson 		const struct usb_descriptor *desc;
35594c21be9bSRebecca Cran 		const struct usb_audio_input_terminal *it;
35604c21be9bSRebecca Cran 		const struct usb_audio_output_terminal *ot;
35614c21be9bSRebecca Cran 		const struct usb_audio_mixer_unit_0 *mu;
35624c21be9bSRebecca Cran 		const struct usb_audio_selector_unit *su;
35634c21be9bSRebecca Cran 		const struct usb_audio_feature_unit *fu;
35644c21be9bSRebecca Cran 		const struct usb_audio_processing_unit_0 *pu;
35654c21be9bSRebecca Cran 		const struct usb_audio_extension_unit_0 *eu;
35663a3f90c6SAndrew Thompson 	}     u;
35673a3f90c6SAndrew Thompson 
35683a3f90c6SAndrew Thompson 	u.desc = arg;
35693a3f90c6SAndrew Thompson 
35703a3f90c6SAndrew Thompson 	if (u.desc == NULL) {
35713a3f90c6SAndrew Thompson 		goto error;
35723a3f90c6SAndrew Thompson 	}
35733a3f90c6SAndrew Thompson 	if (u.desc->bDescriptorType != UDESC_CS_INTERFACE) {
35743a3f90c6SAndrew Thompson 		goto error;
35753a3f90c6SAndrew Thompson 	}
35763a3f90c6SAndrew Thompson 	switch (u.desc->bDescriptorSubtype) {
35773a3f90c6SAndrew Thompson 	case UDESCSUB_AC_INPUT:
35783a3f90c6SAndrew Thompson 		len += sizeof(*u.it);
35793a3f90c6SAndrew Thompson 		break;
35803a3f90c6SAndrew Thompson 
35813a3f90c6SAndrew Thompson 	case UDESCSUB_AC_OUTPUT:
35823a3f90c6SAndrew Thompson 		len += sizeof(*u.ot);
35833a3f90c6SAndrew Thompson 		break;
35843a3f90c6SAndrew Thompson 
35853a3f90c6SAndrew Thompson 	case UDESCSUB_AC_MIXER:
35863a3f90c6SAndrew Thompson 		len += sizeof(*u.mu);
35873a3f90c6SAndrew Thompson 
35883a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
35893a3f90c6SAndrew Thompson 			goto error;
35903a3f90c6SAndrew Thompson 		}
35913a3f90c6SAndrew Thompson 		len += u.mu->bNrInPins;
35923a3f90c6SAndrew Thompson 
35933a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
35943a3f90c6SAndrew Thompson 			goto error;
35953a3f90c6SAndrew Thompson 		}
35963a3f90c6SAndrew Thompson 		d1 = (const void *)(u.mu->baSourceId + u.mu->bNrInPins);
35973a3f90c6SAndrew Thompson 
35983a3f90c6SAndrew Thompson 		len += sizeof(*d1);
35993a3f90c6SAndrew Thompson 		break;
36003a3f90c6SAndrew Thompson 
36013a3f90c6SAndrew Thompson 	case UDESCSUB_AC_SELECTOR:
36023a3f90c6SAndrew Thompson 		len += sizeof(*u.su);
36033a3f90c6SAndrew Thompson 
36043a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
36053a3f90c6SAndrew Thompson 			goto error;
36063a3f90c6SAndrew Thompson 		}
3607902514f6SHans Petter Selasky 		len += u.su->bNrInPins + 1;
36083a3f90c6SAndrew Thompson 		break;
36093a3f90c6SAndrew Thompson 
36103a3f90c6SAndrew Thompson 	case UDESCSUB_AC_FEATURE:
3611902514f6SHans Petter Selasky 		len += sizeof(*u.fu) + 1;
3612902514f6SHans Petter Selasky 
3613902514f6SHans Petter Selasky 		if (u.desc->bLength < len)
3614902514f6SHans Petter Selasky 			goto error;
3615902514f6SHans Petter Selasky 
3616902514f6SHans Petter Selasky 		len += u.fu->bControlSize;
36173a3f90c6SAndrew Thompson 		break;
36183a3f90c6SAndrew Thompson 
36193a3f90c6SAndrew Thompson 	case UDESCSUB_AC_PROCESSING:
36203a3f90c6SAndrew Thompson 		len += sizeof(*u.pu);
36213a3f90c6SAndrew Thompson 
36223a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
36233a3f90c6SAndrew Thompson 			goto error;
36243a3f90c6SAndrew Thompson 		}
36253a3f90c6SAndrew Thompson 		len += u.pu->bNrInPins;
36263a3f90c6SAndrew Thompson 
36273a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
36283a3f90c6SAndrew Thompson 			goto error;
36293a3f90c6SAndrew Thompson 		}
36303a3f90c6SAndrew Thompson 		u1 = (const void *)(u.pu->baSourceId + u.pu->bNrInPins);
36313a3f90c6SAndrew Thompson 
36323a3f90c6SAndrew Thompson 		len += sizeof(*u1);
36333a3f90c6SAndrew Thompson 
36343a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
36353a3f90c6SAndrew Thompson 			goto error;
36363a3f90c6SAndrew Thompson 		}
36373a3f90c6SAndrew Thompson 		len += u1->bControlSize;
36383a3f90c6SAndrew Thompson 
36393a3f90c6SAndrew Thompson 		break;
36403a3f90c6SAndrew Thompson 
36413a3f90c6SAndrew Thompson 	case UDESCSUB_AC_EXTENSION:
36423a3f90c6SAndrew Thompson 		len += sizeof(*u.eu);
36433a3f90c6SAndrew Thompson 
36443a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
36453a3f90c6SAndrew Thompson 			goto error;
36463a3f90c6SAndrew Thompson 		}
36473a3f90c6SAndrew Thompson 		len += u.eu->bNrInPins;
36483a3f90c6SAndrew Thompson 
36493a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
36503a3f90c6SAndrew Thompson 			goto error;
36513a3f90c6SAndrew Thompson 		}
36523a3f90c6SAndrew Thompson 		e1 = (const void *)(u.eu->baSourceId + u.eu->bNrInPins);
36533a3f90c6SAndrew Thompson 
36543a3f90c6SAndrew Thompson 		len += sizeof(*e1);
36553a3f90c6SAndrew Thompson 
36563a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
36573a3f90c6SAndrew Thompson 			goto error;
36583a3f90c6SAndrew Thompson 		}
36593a3f90c6SAndrew Thompson 		len += e1->bControlSize;
36603a3f90c6SAndrew Thompson 		break;
36613a3f90c6SAndrew Thompson 
36623a3f90c6SAndrew Thompson 	default:
36633a3f90c6SAndrew Thompson 		goto error;
36643a3f90c6SAndrew Thompson 	}
36653a3f90c6SAndrew Thompson 
36663a3f90c6SAndrew Thompson 	if (u.desc->bLength < len) {
36673a3f90c6SAndrew Thompson 		goto error;
36683a3f90c6SAndrew Thompson 	}
36693a3f90c6SAndrew Thompson 	return (u.desc);
36703a3f90c6SAndrew Thompson 
36713a3f90c6SAndrew Thompson error:
36723a3f90c6SAndrew Thompson 	if (u.desc) {
36733a3f90c6SAndrew Thompson 		DPRINTF("invalid descriptor, type=%d, "
36743a3f90c6SAndrew Thompson 		    "sub_type=%d, len=%d of %d bytes\n",
36753a3f90c6SAndrew Thompson 		    u.desc->bDescriptorType,
36763a3f90c6SAndrew Thompson 		    u.desc->bDescriptorSubtype,
36773a3f90c6SAndrew Thompson 		    u.desc->bLength, len);
36783a3f90c6SAndrew Thompson 	}
36793a3f90c6SAndrew Thompson 	return (NULL);
36803a3f90c6SAndrew Thompson }
36813a3f90c6SAndrew Thompson 
3682e2524b2eSHans Petter Selasky static const void *
3683e2524b2eSHans Petter Selasky uaudio20_mixer_verify_desc(const void *arg, uint32_t len)
36843a3f90c6SAndrew Thompson {
3685e2524b2eSHans Petter Selasky 	const struct usb_audio20_mixer_unit_1 *d1;
3686e2524b2eSHans Petter Selasky 	const struct usb_audio20_extension_unit_1 *e1;
3687e2524b2eSHans Petter Selasky 	const struct usb_audio20_processing_unit_1 *u1;
3688e2524b2eSHans Petter Selasky 	const struct usb_audio20_clock_selector_unit_1 *c1;
36893a3f90c6SAndrew Thompson 
3690e2524b2eSHans Petter Selasky 	union {
3691e2524b2eSHans Petter Selasky 		const struct usb_descriptor *desc;
3692e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_source_unit *csrc;
3693e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_selector_unit_0 *csel;
3694e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_multiplier_unit *cmul;
3695e2524b2eSHans Petter Selasky 		const struct usb_audio20_input_terminal *it;
3696e2524b2eSHans Petter Selasky 		const struct usb_audio20_output_terminal *ot;
3697e2524b2eSHans Petter Selasky 		const struct usb_audio20_mixer_unit_0 *mu;
3698e2524b2eSHans Petter Selasky 		const struct usb_audio20_selector_unit *su;
3699e2524b2eSHans Petter Selasky 		const struct usb_audio20_feature_unit *fu;
3700e2524b2eSHans Petter Selasky 		const struct usb_audio20_sample_rate_unit *ru;
3701e2524b2eSHans Petter Selasky 		const struct usb_audio20_processing_unit_0 *pu;
3702e2524b2eSHans Petter Selasky 		const struct usb_audio20_extension_unit_0 *eu;
3703e2524b2eSHans Petter Selasky 		const struct usb_audio20_effect_unit *ef;
3704e2524b2eSHans Petter Selasky 	}     u;
37053a3f90c6SAndrew Thompson 
3706e2524b2eSHans Petter Selasky 	u.desc = arg;
37073a3f90c6SAndrew Thompson 
3708e2524b2eSHans Petter Selasky 	if (u.desc == NULL)
3709e2524b2eSHans Petter Selasky 		goto error;
3710e2524b2eSHans Petter Selasky 
3711e2524b2eSHans Petter Selasky 	if (u.desc->bDescriptorType != UDESC_CS_INTERFACE)
3712e2524b2eSHans Petter Selasky 		goto error;
3713e2524b2eSHans Petter Selasky 
3714e2524b2eSHans Petter Selasky 	switch (u.desc->bDescriptorSubtype) {
3715e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_INPUT:
3716e2524b2eSHans Petter Selasky 		len += sizeof(*u.it);
3717e2524b2eSHans Petter Selasky 		break;
3718e2524b2eSHans Petter Selasky 
3719e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_OUTPUT:
3720e2524b2eSHans Petter Selasky 		len += sizeof(*u.ot);
3721e2524b2eSHans Petter Selasky 		break;
3722e2524b2eSHans Petter Selasky 
3723e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_MIXER:
3724e2524b2eSHans Petter Selasky 		len += sizeof(*u.mu);
3725e2524b2eSHans Petter Selasky 
3726e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
3727e2524b2eSHans Petter Selasky 			goto error;
3728e2524b2eSHans Petter Selasky 		len += u.mu->bNrInPins;
3729e2524b2eSHans Petter Selasky 
3730e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
3731e2524b2eSHans Petter Selasky 			goto error;
3732e2524b2eSHans Petter Selasky 
3733e2524b2eSHans Petter Selasky 		d1 = (const void *)(u.mu->baSourceId + u.mu->bNrInPins);
3734e2524b2eSHans Petter Selasky 
3735e2524b2eSHans Petter Selasky 		len += sizeof(*d1) + d1->bNrChannels;
3736e2524b2eSHans Petter Selasky 		break;
3737e2524b2eSHans Petter Selasky 
3738e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_SELECTOR:
3739e2524b2eSHans Petter Selasky 		len += sizeof(*u.su);
3740e2524b2eSHans Petter Selasky 
3741e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
3742e2524b2eSHans Petter Selasky 			goto error;
3743e2524b2eSHans Petter Selasky 
3744902514f6SHans Petter Selasky 		len += u.su->bNrInPins + 1;
3745e2524b2eSHans Petter Selasky 		break;
3746e2524b2eSHans Petter Selasky 
3747e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_FEATURE:
3748e2524b2eSHans Petter Selasky 		len += sizeof(*u.fu) + 1;
3749e2524b2eSHans Petter Selasky 		break;
3750e2524b2eSHans Petter Selasky 
3751e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_EFFECT:
3752e2524b2eSHans Petter Selasky 		len += sizeof(*u.ef) + 4;
3753e2524b2eSHans Petter Selasky 		break;
3754e2524b2eSHans Petter Selasky 
3755e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_PROCESSING_V2:
3756e2524b2eSHans Petter Selasky 		len += sizeof(*u.pu);
3757e2524b2eSHans Petter Selasky 
3758e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
3759e2524b2eSHans Petter Selasky 			goto error;
3760e2524b2eSHans Petter Selasky 
3761e2524b2eSHans Petter Selasky 		len += u.pu->bNrInPins;
3762e2524b2eSHans Petter Selasky 
3763e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
3764e2524b2eSHans Petter Selasky 			goto error;
3765e2524b2eSHans Petter Selasky 
3766e2524b2eSHans Petter Selasky 		u1 = (const void *)(u.pu->baSourceId + u.pu->bNrInPins);
3767e2524b2eSHans Petter Selasky 
3768e2524b2eSHans Petter Selasky 		len += sizeof(*u1);
3769e2524b2eSHans Petter Selasky 		break;
3770e2524b2eSHans Petter Selasky 
3771e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_EXTENSION_V2:
3772e2524b2eSHans Petter Selasky 		len += sizeof(*u.eu);
3773e2524b2eSHans Petter Selasky 
3774e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
3775e2524b2eSHans Petter Selasky 			goto error;
3776e2524b2eSHans Petter Selasky 
3777e2524b2eSHans Petter Selasky 		len += u.eu->bNrInPins;
3778e2524b2eSHans Petter Selasky 
3779e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
3780e2524b2eSHans Petter Selasky 			goto error;
3781e2524b2eSHans Petter Selasky 
3782e2524b2eSHans Petter Selasky 		e1 = (const void *)(u.eu->baSourceId + u.eu->bNrInPins);
3783e2524b2eSHans Petter Selasky 
3784e2524b2eSHans Petter Selasky 		len += sizeof(*e1);
3785e2524b2eSHans Petter Selasky 		break;
3786e2524b2eSHans Petter Selasky 
3787e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_CLOCK_SRC:
3788e2524b2eSHans Petter Selasky 		len += sizeof(*u.csrc);
3789e2524b2eSHans Petter Selasky 		break;
3790e2524b2eSHans Petter Selasky 
3791e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_CLOCK_SEL:
3792e2524b2eSHans Petter Selasky 		len += sizeof(*u.csel);
3793e2524b2eSHans Petter Selasky 
3794e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
3795e2524b2eSHans Petter Selasky 			goto error;
3796e2524b2eSHans Petter Selasky 
3797e2524b2eSHans Petter Selasky 		len += u.csel->bNrInPins;
3798e2524b2eSHans Petter Selasky 
3799e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
3800e2524b2eSHans Petter Selasky 			goto error;
3801e2524b2eSHans Petter Selasky 
3802e2524b2eSHans Petter Selasky 		c1 = (const void *)(u.csel->baCSourceId + u.csel->bNrInPins);
3803e2524b2eSHans Petter Selasky 
3804e2524b2eSHans Petter Selasky 		len += sizeof(*c1);
3805e2524b2eSHans Petter Selasky 		break;
3806e2524b2eSHans Petter Selasky 
3807e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_CLOCK_MUL:
3808e2524b2eSHans Petter Selasky 		len += sizeof(*u.cmul);
3809e2524b2eSHans Petter Selasky 		break;
3810e2524b2eSHans Petter Selasky 
3811e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_SAMPLE_RT:
3812e2524b2eSHans Petter Selasky 		len += sizeof(*u.ru);
3813e2524b2eSHans Petter Selasky 		break;
3814e2524b2eSHans Petter Selasky 
3815e2524b2eSHans Petter Selasky 	default:
3816e2524b2eSHans Petter Selasky 		goto error;
38173a3f90c6SAndrew Thompson 	}
38183a3f90c6SAndrew Thompson 
3819e2524b2eSHans Petter Selasky 	if (u.desc->bLength < len)
3820e2524b2eSHans Petter Selasky 		goto error;
3821e2524b2eSHans Petter Selasky 
3822e2524b2eSHans Petter Selasky 	return (u.desc);
3823e2524b2eSHans Petter Selasky 
3824e2524b2eSHans Petter Selasky error:
3825e2524b2eSHans Petter Selasky 	if (u.desc) {
3826e2524b2eSHans Petter Selasky 		DPRINTF("invalid descriptor, type=%d, "
3827e2524b2eSHans Petter Selasky 		    "sub_type=%d, len=%d of %d bytes\n",
3828e2524b2eSHans Petter Selasky 		    u.desc->bDescriptorType,
3829e2524b2eSHans Petter Selasky 		    u.desc->bDescriptorSubtype,
3830e2524b2eSHans Petter Selasky 		    u.desc->bLength, len);
3831e2524b2eSHans Petter Selasky 	}
3832e2524b2eSHans Petter Selasky 	return (NULL);
3833e2524b2eSHans Petter Selasky }
38343a3f90c6SAndrew Thompson 
38354c21be9bSRebecca Cran static struct usb_audio_cluster
38363a3f90c6SAndrew Thompson uaudio_mixer_get_cluster(uint8_t id, const struct uaudio_terminal_node *iot)
38373a3f90c6SAndrew Thompson {
38384c21be9bSRebecca Cran 	struct usb_audio_cluster r;
3839760bc48eSAndrew Thompson 	const struct usb_descriptor *dp;
38403a3f90c6SAndrew Thompson 	uint8_t i;
38413a3f90c6SAndrew Thompson 
38423a3f90c6SAndrew Thompson 	for (i = 0; i < UAUDIO_RECURSE_LIMIT; i++) {	/* avoid infinite loops */
38433a3f90c6SAndrew Thompson 		dp = iot[id].u.desc;
38443a3f90c6SAndrew Thompson 		if (dp == NULL) {
38453a3f90c6SAndrew Thompson 			goto error;
38463a3f90c6SAndrew Thompson 		}
38473a3f90c6SAndrew Thompson 		switch (dp->bDescriptorSubtype) {
38483a3f90c6SAndrew Thompson 		case UDESCSUB_AC_INPUT:
3849e2524b2eSHans Petter Selasky 			r.bNrChannels = iot[id].u.it_v1->bNrChannels;
3850e2524b2eSHans Petter Selasky 			r.wChannelConfig[0] = iot[id].u.it_v1->wChannelConfig[0];
3851e2524b2eSHans Petter Selasky 			r.wChannelConfig[1] = iot[id].u.it_v1->wChannelConfig[1];
3852e2524b2eSHans Petter Selasky 			r.iChannelNames = iot[id].u.it_v1->iChannelNames;
38533a3f90c6SAndrew Thompson 			goto done;
38543a3f90c6SAndrew Thompson 
38553a3f90c6SAndrew Thompson 		case UDESCSUB_AC_OUTPUT:
3856e2524b2eSHans Petter Selasky 			id = iot[id].u.ot_v1->bSourceId;
38573a3f90c6SAndrew Thompson 			break;
38583a3f90c6SAndrew Thompson 
38593a3f90c6SAndrew Thompson 		case UDESCSUB_AC_MIXER:
38604c21be9bSRebecca Cran 			r = *(const struct usb_audio_cluster *)
3861e2524b2eSHans Petter Selasky 			    &iot[id].u.mu_v1->baSourceId[
3862e2524b2eSHans Petter Selasky 			    iot[id].u.mu_v1->bNrInPins];
38633a3f90c6SAndrew Thompson 			goto done;
38643a3f90c6SAndrew Thompson 
38653a3f90c6SAndrew Thompson 		case UDESCSUB_AC_SELECTOR:
3866e2524b2eSHans Petter Selasky 			if (iot[id].u.su_v1->bNrInPins > 0) {
38673a3f90c6SAndrew Thompson 				/* XXX This is not really right */
3868e2524b2eSHans Petter Selasky 				id = iot[id].u.su_v1->baSourceId[0];
38693a3f90c6SAndrew Thompson 			}
38703a3f90c6SAndrew Thompson 			break;
38713a3f90c6SAndrew Thompson 
38723a3f90c6SAndrew Thompson 		case UDESCSUB_AC_FEATURE:
3873e2524b2eSHans Petter Selasky 			id = iot[id].u.fu_v1->bSourceId;
38743a3f90c6SAndrew Thompson 			break;
38753a3f90c6SAndrew Thompson 
38763a3f90c6SAndrew Thompson 		case UDESCSUB_AC_PROCESSING:
38774c21be9bSRebecca Cran 			r = *((const struct usb_audio_cluster *)
3878e2524b2eSHans Petter Selasky 			    &iot[id].u.pu_v1->baSourceId[
3879e2524b2eSHans Petter Selasky 			    iot[id].u.pu_v1->bNrInPins]);
38803a3f90c6SAndrew Thompson 			goto done;
38813a3f90c6SAndrew Thompson 
38823a3f90c6SAndrew Thompson 		case UDESCSUB_AC_EXTENSION:
38834c21be9bSRebecca Cran 			r = *((const struct usb_audio_cluster *)
3884e2524b2eSHans Petter Selasky 			    &iot[id].u.eu_v1->baSourceId[
3885e2524b2eSHans Petter Selasky 			    iot[id].u.eu_v1->bNrInPins]);
38863a3f90c6SAndrew Thompson 			goto done;
38873a3f90c6SAndrew Thompson 
38883a3f90c6SAndrew Thompson 		default:
38893a3f90c6SAndrew Thompson 			goto error;
38903a3f90c6SAndrew Thompson 		}
38913a3f90c6SAndrew Thompson 	}
38923a3f90c6SAndrew Thompson error:
38933a3f90c6SAndrew Thompson 	DPRINTF("bad data\n");
38946f068a43SHans Petter Selasky 	memset(&r, 0, sizeof(r));
38953a3f90c6SAndrew Thompson done:
38963a3f90c6SAndrew Thompson 	return (r);
38973a3f90c6SAndrew Thompson }
38983a3f90c6SAndrew Thompson 
3899e2524b2eSHans Petter Selasky static struct usb_audio20_cluster
3900e2524b2eSHans Petter Selasky uaudio20_mixer_get_cluster(uint8_t id, const struct uaudio_terminal_node *iot)
39013a3f90c6SAndrew Thompson {
3902e2524b2eSHans Petter Selasky 	struct usb_audio20_cluster r;
3903e2524b2eSHans Petter Selasky 	const struct usb_descriptor *dp;
3904e2524b2eSHans Petter Selasky 	uint8_t i;
39053a3f90c6SAndrew Thompson 
3906e2524b2eSHans Petter Selasky 	for (i = 0; i < UAUDIO_RECURSE_LIMIT; i++) {	/* avoid infinite loops */
3907e2524b2eSHans Petter Selasky 		dp = iot[id].u.desc;
3908e2524b2eSHans Petter Selasky 		if (dp == NULL)
3909e2524b2eSHans Petter Selasky 			goto error;
3910e2524b2eSHans Petter Selasky 
3911e2524b2eSHans Petter Selasky 		switch (dp->bDescriptorSubtype) {
3912e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_INPUT:
3913e2524b2eSHans Petter Selasky 			r.bNrChannels = iot[id].u.it_v2->bNrChannels;
3914e2524b2eSHans Petter Selasky 			r.bmChannelConfig[0] = iot[id].u.it_v2->bmChannelConfig[0];
3915e2524b2eSHans Petter Selasky 			r.bmChannelConfig[1] = iot[id].u.it_v2->bmChannelConfig[1];
3916e2524b2eSHans Petter Selasky 			r.bmChannelConfig[2] = iot[id].u.it_v2->bmChannelConfig[2];
3917e2524b2eSHans Petter Selasky 			r.bmChannelConfig[3] = iot[id].u.it_v2->bmChannelConfig[3];
3918e2524b2eSHans Petter Selasky 			r.iChannelNames = iot[id].u.it_v2->iTerminal;
3919e2524b2eSHans Petter Selasky 			goto done;
3920e2524b2eSHans Petter Selasky 
3921e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_OUTPUT:
3922e2524b2eSHans Petter Selasky 			id = iot[id].u.ot_v2->bSourceId;
39233a3f90c6SAndrew Thompson 			break;
39243a3f90c6SAndrew Thompson 
3925e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_MIXER:
3926e2524b2eSHans Petter Selasky 			r = *(const struct usb_audio20_cluster *)
3927e2524b2eSHans Petter Selasky 			    &iot[id].u.mu_v2->baSourceId[
3928e2524b2eSHans Petter Selasky 			    iot[id].u.mu_v2->bNrInPins];
3929e2524b2eSHans Petter Selasky 			goto done;
3930e2524b2eSHans Petter Selasky 
3931e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_SELECTOR:
3932e2524b2eSHans Petter Selasky 			if (iot[id].u.su_v2->bNrInPins > 0) {
3933e2524b2eSHans Petter Selasky 				/* XXX This is not really right */
3934e2524b2eSHans Petter Selasky 				id = iot[id].u.su_v2->baSourceId[0];
3935e2524b2eSHans Petter Selasky 			}
3936e2524b2eSHans Petter Selasky 			break;
3937e2524b2eSHans Petter Selasky 
3938e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_SAMPLE_RT:
3939e2524b2eSHans Petter Selasky 			id = iot[id].u.ru_v2->bSourceId;
3940e2524b2eSHans Petter Selasky 			break;
3941e2524b2eSHans Petter Selasky 
3942e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_EFFECT:
3943e2524b2eSHans Petter Selasky 			id = iot[id].u.ef_v2->bSourceId;
3944e2524b2eSHans Petter Selasky 			break;
3945e2524b2eSHans Petter Selasky 
3946e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_FEATURE:
3947e2524b2eSHans Petter Selasky 			id = iot[id].u.fu_v2->bSourceId;
3948e2524b2eSHans Petter Selasky 			break;
3949e2524b2eSHans Petter Selasky 
3950e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_PROCESSING_V2:
3951e2524b2eSHans Petter Selasky 			r = *((const struct usb_audio20_cluster *)
3952e2524b2eSHans Petter Selasky 			    &iot[id].u.pu_v2->baSourceId[
3953e2524b2eSHans Petter Selasky 			    iot[id].u.pu_v2->bNrInPins]);
3954e2524b2eSHans Petter Selasky 			goto done;
3955e2524b2eSHans Petter Selasky 
3956e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_EXTENSION_V2:
3957e2524b2eSHans Petter Selasky 			r = *((const struct usb_audio20_cluster *)
3958e2524b2eSHans Petter Selasky 			    &iot[id].u.eu_v2->baSourceId[
3959e2524b2eSHans Petter Selasky 			    iot[id].u.eu_v2->bNrInPins]);
3960e2524b2eSHans Petter Selasky 			goto done;
3961e2524b2eSHans Petter Selasky 
3962e2524b2eSHans Petter Selasky 		default:
3963e2524b2eSHans Petter Selasky 			goto error;
3964e2524b2eSHans Petter Selasky 		}
3965e2524b2eSHans Petter Selasky 	}
3966e2524b2eSHans Petter Selasky error:
3967e2524b2eSHans Petter Selasky 	DPRINTF("Bad data!\n");
3968e2524b2eSHans Petter Selasky 	memset(&r, 0, sizeof(r));
3969e2524b2eSHans Petter Selasky done:
3970e2524b2eSHans Petter Selasky 	return (r);
3971e2524b2eSHans Petter Selasky }
39723a3f90c6SAndrew Thompson 
39733a3f90c6SAndrew Thompson static uint16_t
39743a3f90c6SAndrew Thompson uaudio_mixer_determine_class(const struct uaudio_terminal_node *iot,
39753a3f90c6SAndrew Thompson     struct uaudio_mixer_node *mix)
39763a3f90c6SAndrew Thompson {
39773a3f90c6SAndrew Thompson 	uint16_t terminal_type = 0x0000;
39783a3f90c6SAndrew Thompson 	const struct uaudio_terminal_node *input[2];
39793a3f90c6SAndrew Thompson 	const struct uaudio_terminal_node *output[2];
39803a3f90c6SAndrew Thompson 
39813a3f90c6SAndrew Thompson 	input[0] = uaudio_mixer_get_input(iot, 0);
39823a3f90c6SAndrew Thompson 	input[1] = uaudio_mixer_get_input(iot, 1);
39833a3f90c6SAndrew Thompson 
39843a3f90c6SAndrew Thompson 	output[0] = uaudio_mixer_get_output(iot, 0);
39853a3f90c6SAndrew Thompson 	output[1] = uaudio_mixer_get_output(iot, 1);
39863a3f90c6SAndrew Thompson 
39873a3f90c6SAndrew Thompson 	/*
39883a3f90c6SAndrew Thompson 	 * check if there is only
39893a3f90c6SAndrew Thompson 	 * one output terminal:
39903a3f90c6SAndrew Thompson 	 */
39913a3f90c6SAndrew Thompson 	if (output[0] && (!output[1])) {
3992e2524b2eSHans Petter Selasky 		terminal_type =
3993e2524b2eSHans Petter Selasky 		    UGETW(output[0]->u.ot_v1->wTerminalType);
39943a3f90c6SAndrew Thompson 	}
39953a3f90c6SAndrew Thompson 	/*
39963a3f90c6SAndrew Thompson 	 * If the only output terminal is USB,
39973a3f90c6SAndrew Thompson 	 * the class is UAC_RECORD.
39983a3f90c6SAndrew Thompson 	 */
39993a3f90c6SAndrew Thompson 	if ((terminal_type & 0xff00) == (UAT_UNDEFINED & 0xff00)) {
40003a3f90c6SAndrew Thompson 
40013a3f90c6SAndrew Thompson 		mix->class = UAC_RECORD;
40023a3f90c6SAndrew Thompson 		if (input[0] && (!input[1])) {
4003e2524b2eSHans Petter Selasky 			terminal_type =
4004e2524b2eSHans Petter Selasky 			    UGETW(input[0]->u.it_v1->wTerminalType);
40053a3f90c6SAndrew Thompson 		} else {
40063a3f90c6SAndrew Thompson 			terminal_type = 0;
40073a3f90c6SAndrew Thompson 		}
40083a3f90c6SAndrew Thompson 		goto done;
40093a3f90c6SAndrew Thompson 	}
40103a3f90c6SAndrew Thompson 	/*
40113a3f90c6SAndrew Thompson 	 * if the unit is connected to just
40123a3f90c6SAndrew Thompson 	 * one input terminal, the
40133a3f90c6SAndrew Thompson 	 * class is UAC_INPUT:
40143a3f90c6SAndrew Thompson 	 */
40153a3f90c6SAndrew Thompson 	if (input[0] && (!input[1])) {
40163a3f90c6SAndrew Thompson 		mix->class = UAC_INPUT;
4017e2524b2eSHans Petter Selasky 		terminal_type =
4018e2524b2eSHans Petter Selasky 		    UGETW(input[0]->u.it_v1->wTerminalType);
4019e2524b2eSHans Petter Selasky 		goto done;
4020e2524b2eSHans Petter Selasky 	}
4021e2524b2eSHans Petter Selasky 	/*
4022e2524b2eSHans Petter Selasky 	 * Otherwise, the class is UAC_OUTPUT.
4023e2524b2eSHans Petter Selasky 	 */
4024e2524b2eSHans Petter Selasky 	mix->class = UAC_OUTPUT;
4025e2524b2eSHans Petter Selasky done:
4026e2524b2eSHans Petter Selasky 	return (terminal_type);
4027e2524b2eSHans Petter Selasky }
4028e2524b2eSHans Petter Selasky 
4029e2524b2eSHans Petter Selasky static uint16_t
4030e2524b2eSHans Petter Selasky uaudio20_mixer_determine_class(const struct uaudio_terminal_node *iot,
4031e2524b2eSHans Petter Selasky     struct uaudio_mixer_node *mix)
4032e2524b2eSHans Petter Selasky {
4033e2524b2eSHans Petter Selasky 	uint16_t terminal_type = 0x0000;
4034e2524b2eSHans Petter Selasky 	const struct uaudio_terminal_node *input[2];
4035e2524b2eSHans Petter Selasky 	const struct uaudio_terminal_node *output[2];
4036e2524b2eSHans Petter Selasky 
4037e2524b2eSHans Petter Selasky 	input[0] = uaudio_mixer_get_input(iot, 0);
4038e2524b2eSHans Petter Selasky 	input[1] = uaudio_mixer_get_input(iot, 1);
4039e2524b2eSHans Petter Selasky 
4040e2524b2eSHans Petter Selasky 	output[0] = uaudio_mixer_get_output(iot, 0);
4041e2524b2eSHans Petter Selasky 	output[1] = uaudio_mixer_get_output(iot, 1);
4042e2524b2eSHans Petter Selasky 
4043e2524b2eSHans Petter Selasky 	/*
4044e2524b2eSHans Petter Selasky 	 * check if there is only
4045e2524b2eSHans Petter Selasky 	 * one output terminal:
4046e2524b2eSHans Petter Selasky 	 */
4047e2524b2eSHans Petter Selasky 	if (output[0] && (!output[1]))
4048e2524b2eSHans Petter Selasky 		terminal_type = UGETW(output[0]->u.ot_v2->wTerminalType);
4049e2524b2eSHans Petter Selasky 	/*
4050e2524b2eSHans Petter Selasky 	 * If the only output terminal is USB,
4051e2524b2eSHans Petter Selasky 	 * the class is UAC_RECORD.
4052e2524b2eSHans Petter Selasky 	 */
4053e2524b2eSHans Petter Selasky 	if ((terminal_type & 0xff00) == (UAT_UNDEFINED & 0xff00)) {
4054e2524b2eSHans Petter Selasky 
4055e2524b2eSHans Petter Selasky 		mix->class = UAC_RECORD;
4056e2524b2eSHans Petter Selasky 		if (input[0] && (!input[1])) {
4057e2524b2eSHans Petter Selasky 			terminal_type =
4058e2524b2eSHans Petter Selasky 			    UGETW(input[0]->u.it_v2->wTerminalType);
4059e2524b2eSHans Petter Selasky 		} else {
4060e2524b2eSHans Petter Selasky 			terminal_type = 0;
4061e2524b2eSHans Petter Selasky 		}
4062e2524b2eSHans Petter Selasky 		goto done;
4063e2524b2eSHans Petter Selasky 	}
4064e2524b2eSHans Petter Selasky 	/*
4065e2524b2eSHans Petter Selasky 	 * if the unit is connected to just
4066e2524b2eSHans Petter Selasky 	 * one input terminal, the
4067e2524b2eSHans Petter Selasky 	 * class is UAC_INPUT:
4068e2524b2eSHans Petter Selasky 	 */
4069e2524b2eSHans Petter Selasky 	if (input[0] && (!input[1])) {
4070e2524b2eSHans Petter Selasky 		mix->class = UAC_INPUT;
4071e2524b2eSHans Petter Selasky 		terminal_type =
4072e2524b2eSHans Petter Selasky 		    UGETW(input[0]->u.it_v2->wTerminalType);
40733a3f90c6SAndrew Thompson 		goto done;
40743a3f90c6SAndrew Thompson 	}
40753a3f90c6SAndrew Thompson 	/*
40763a3f90c6SAndrew Thompson 	 * Otherwise, the class is UAC_OUTPUT.
40773a3f90c6SAndrew Thompson 	 */
40783a3f90c6SAndrew Thompson 	mix->class = UAC_OUTPUT;
40793a3f90c6SAndrew Thompson done:
40803a3f90c6SAndrew Thompson 	return (terminal_type);
40813a3f90c6SAndrew Thompson }
40823a3f90c6SAndrew Thompson 
40833a3f90c6SAndrew Thompson struct uaudio_tt_to_feature {
40843a3f90c6SAndrew Thompson 	uint16_t terminal_type;
40853a3f90c6SAndrew Thompson 	uint16_t feature;
40863a3f90c6SAndrew Thompson };
40873a3f90c6SAndrew Thompson 
40883a3f90c6SAndrew Thompson static const struct uaudio_tt_to_feature uaudio_tt_to_feature[] = {
40893a3f90c6SAndrew Thompson 
40903a3f90c6SAndrew Thompson 	{UAT_STREAM, SOUND_MIXER_PCM},
40913a3f90c6SAndrew Thompson 
40923a3f90c6SAndrew Thompson 	{UATI_MICROPHONE, SOUND_MIXER_MIC},
40933a3f90c6SAndrew Thompson 	{UATI_DESKMICROPHONE, SOUND_MIXER_MIC},
40943a3f90c6SAndrew Thompson 	{UATI_PERSONALMICROPHONE, SOUND_MIXER_MIC},
40953a3f90c6SAndrew Thompson 	{UATI_OMNIMICROPHONE, SOUND_MIXER_MIC},
40963a3f90c6SAndrew Thompson 	{UATI_MICROPHONEARRAY, SOUND_MIXER_MIC},
40973a3f90c6SAndrew Thompson 	{UATI_PROCMICROPHONEARR, SOUND_MIXER_MIC},
40983a3f90c6SAndrew Thompson 
40993a3f90c6SAndrew Thompson 	{UATO_SPEAKER, SOUND_MIXER_SPEAKER},
41003a3f90c6SAndrew Thompson 	{UATO_DESKTOPSPEAKER, SOUND_MIXER_SPEAKER},
41013a3f90c6SAndrew Thompson 	{UATO_ROOMSPEAKER, SOUND_MIXER_SPEAKER},
41023a3f90c6SAndrew Thompson 	{UATO_COMMSPEAKER, SOUND_MIXER_SPEAKER},
41033a3f90c6SAndrew Thompson 
41043a3f90c6SAndrew Thompson 	{UATE_ANALOGCONN, SOUND_MIXER_LINE},
41053a3f90c6SAndrew Thompson 	{UATE_LINECONN, SOUND_MIXER_LINE},
41063a3f90c6SAndrew Thompson 	{UATE_LEGACYCONN, SOUND_MIXER_LINE},
41073a3f90c6SAndrew Thompson 
41083a3f90c6SAndrew Thompson 	{UATE_DIGITALAUIFC, SOUND_MIXER_ALTPCM},
41093a3f90c6SAndrew Thompson 	{UATE_SPDIF, SOUND_MIXER_ALTPCM},
41103a3f90c6SAndrew Thompson 	{UATE_1394DA, SOUND_MIXER_ALTPCM},
41113a3f90c6SAndrew Thompson 	{UATE_1394DV, SOUND_MIXER_ALTPCM},
41123a3f90c6SAndrew Thompson 
41133a3f90c6SAndrew Thompson 	{UATF_CDPLAYER, SOUND_MIXER_CD},
41143a3f90c6SAndrew Thompson 
41153a3f90c6SAndrew Thompson 	{UATF_SYNTHESIZER, SOUND_MIXER_SYNTH},
41163a3f90c6SAndrew Thompson 
41173a3f90c6SAndrew Thompson 	{UATF_VIDEODISCAUDIO, SOUND_MIXER_VIDEO},
41183a3f90c6SAndrew Thompson 	{UATF_DVDAUDIO, SOUND_MIXER_VIDEO},
41193a3f90c6SAndrew Thompson 	{UATF_TVTUNERAUDIO, SOUND_MIXER_VIDEO},
41203a3f90c6SAndrew Thompson 
41213a3f90c6SAndrew Thompson 	/* telephony terminal types */
41223a3f90c6SAndrew Thompson 	{UATT_UNDEFINED, SOUND_MIXER_PHONEIN},	/* SOUND_MIXER_PHONEOUT */
41233a3f90c6SAndrew Thompson 	{UATT_PHONELINE, SOUND_MIXER_PHONEIN},	/* SOUND_MIXER_PHONEOUT */
41243a3f90c6SAndrew Thompson 	{UATT_TELEPHONE, SOUND_MIXER_PHONEIN},	/* SOUND_MIXER_PHONEOUT */
41253a3f90c6SAndrew Thompson 	{UATT_DOWNLINEPHONE, SOUND_MIXER_PHONEIN},	/* SOUND_MIXER_PHONEOUT */
41263a3f90c6SAndrew Thompson 
41273a3f90c6SAndrew Thompson 	{UATF_RADIORECV, SOUND_MIXER_RADIO},
41283a3f90c6SAndrew Thompson 	{UATF_RADIOXMIT, SOUND_MIXER_RADIO},
41293a3f90c6SAndrew Thompson 
41303a3f90c6SAndrew Thompson 	{UAT_UNDEFINED, SOUND_MIXER_VOLUME},
41313a3f90c6SAndrew Thompson 	{UAT_VENDOR, SOUND_MIXER_VOLUME},
41323a3f90c6SAndrew Thompson 	{UATI_UNDEFINED, SOUND_MIXER_VOLUME},
41333a3f90c6SAndrew Thompson 
41343a3f90c6SAndrew Thompson 	/* output terminal types */
41353a3f90c6SAndrew Thompson 	{UATO_UNDEFINED, SOUND_MIXER_VOLUME},
41363a3f90c6SAndrew Thompson 	{UATO_DISPLAYAUDIO, SOUND_MIXER_VOLUME},
41373a3f90c6SAndrew Thompson 	{UATO_SUBWOOFER, SOUND_MIXER_VOLUME},
41383a3f90c6SAndrew Thompson 	{UATO_HEADPHONES, SOUND_MIXER_VOLUME},
41393a3f90c6SAndrew Thompson 
41403a3f90c6SAndrew Thompson 	/* bidir terminal types */
41413a3f90c6SAndrew Thompson 	{UATB_UNDEFINED, SOUND_MIXER_VOLUME},
41423a3f90c6SAndrew Thompson 	{UATB_HANDSET, SOUND_MIXER_VOLUME},
41433a3f90c6SAndrew Thompson 	{UATB_HEADSET, SOUND_MIXER_VOLUME},
41443a3f90c6SAndrew Thompson 	{UATB_SPEAKERPHONE, SOUND_MIXER_VOLUME},
41453a3f90c6SAndrew Thompson 	{UATB_SPEAKERPHONEESUP, SOUND_MIXER_VOLUME},
41463a3f90c6SAndrew Thompson 	{UATB_SPEAKERPHONEECANC, SOUND_MIXER_VOLUME},
41473a3f90c6SAndrew Thompson 
41483a3f90c6SAndrew Thompson 	/* external terminal types */
41493a3f90c6SAndrew Thompson 	{UATE_UNDEFINED, SOUND_MIXER_VOLUME},
41503a3f90c6SAndrew Thompson 
41513a3f90c6SAndrew Thompson 	/* embedded function terminal types */
41523a3f90c6SAndrew Thompson 	{UATF_UNDEFINED, SOUND_MIXER_VOLUME},
41533a3f90c6SAndrew Thompson 	{UATF_CALIBNOISE, SOUND_MIXER_VOLUME},
41543a3f90c6SAndrew Thompson 	{UATF_EQUNOISE, SOUND_MIXER_VOLUME},
41553a3f90c6SAndrew Thompson 	{UATF_DAT, SOUND_MIXER_VOLUME},
41563a3f90c6SAndrew Thompson 	{UATF_DCC, SOUND_MIXER_VOLUME},
41573a3f90c6SAndrew Thompson 	{UATF_MINIDISK, SOUND_MIXER_VOLUME},
41583a3f90c6SAndrew Thompson 	{UATF_ANALOGTAPE, SOUND_MIXER_VOLUME},
41593a3f90c6SAndrew Thompson 	{UATF_PHONOGRAPH, SOUND_MIXER_VOLUME},
41603a3f90c6SAndrew Thompson 	{UATF_VCRAUDIO, SOUND_MIXER_VOLUME},
41613a3f90c6SAndrew Thompson 	{UATF_SATELLITE, SOUND_MIXER_VOLUME},
41623a3f90c6SAndrew Thompson 	{UATF_CABLETUNER, SOUND_MIXER_VOLUME},
41633a3f90c6SAndrew Thompson 	{UATF_DSS, SOUND_MIXER_VOLUME},
41643a3f90c6SAndrew Thompson 	{UATF_MULTITRACK, SOUND_MIXER_VOLUME},
41653a3f90c6SAndrew Thompson 	{0xffff, SOUND_MIXER_VOLUME},
41663a3f90c6SAndrew Thompson 
41673a3f90c6SAndrew Thompson 	/* default */
41683a3f90c6SAndrew Thompson 	{0x0000, SOUND_MIXER_VOLUME},
41693a3f90c6SAndrew Thompson };
41703a3f90c6SAndrew Thompson 
41713a3f90c6SAndrew Thompson static uint16_t
41723a3f90c6SAndrew Thompson uaudio_mixer_feature_name(const struct uaudio_terminal_node *iot,
41733a3f90c6SAndrew Thompson     struct uaudio_mixer_node *mix)
41743a3f90c6SAndrew Thompson {
41753a3f90c6SAndrew Thompson 	const struct uaudio_tt_to_feature *uat = uaudio_tt_to_feature;
41763a3f90c6SAndrew Thompson 	uint16_t terminal_type = uaudio_mixer_determine_class(iot, mix);
41773a3f90c6SAndrew Thompson 
41783a3f90c6SAndrew Thompson 	if ((mix->class == UAC_RECORD) && (terminal_type == 0)) {
41793a3f90c6SAndrew Thompson 		return (SOUND_MIXER_IMIX);
41803a3f90c6SAndrew Thompson 	}
41813a3f90c6SAndrew Thompson 	while (uat->terminal_type) {
41823a3f90c6SAndrew Thompson 		if (uat->terminal_type == terminal_type) {
41833a3f90c6SAndrew Thompson 			break;
41843a3f90c6SAndrew Thompson 		}
41853a3f90c6SAndrew Thompson 		uat++;
41863a3f90c6SAndrew Thompson 	}
41873a3f90c6SAndrew Thompson 
4188e2524b2eSHans Petter Selasky 	DPRINTF("terminal_type=0x%04x -> %d\n",
4189e2524b2eSHans Petter Selasky 	    terminal_type, uat->feature);
4190e2524b2eSHans Petter Selasky 
4191e2524b2eSHans Petter Selasky 	return (uat->feature);
4192e2524b2eSHans Petter Selasky }
4193e2524b2eSHans Petter Selasky 
4194e2524b2eSHans Petter Selasky static uint16_t
4195e2524b2eSHans Petter Selasky uaudio20_mixer_feature_name(const struct uaudio_terminal_node *iot,
4196e2524b2eSHans Petter Selasky     struct uaudio_mixer_node *mix)
4197e2524b2eSHans Petter Selasky {
4198e2524b2eSHans Petter Selasky 	const struct uaudio_tt_to_feature *uat;
4199e2524b2eSHans Petter Selasky 	uint16_t terminal_type = uaudio20_mixer_determine_class(iot, mix);
4200e2524b2eSHans Petter Selasky 
4201e2524b2eSHans Petter Selasky 	if ((mix->class == UAC_RECORD) && (terminal_type == 0))
4202e2524b2eSHans Petter Selasky 		return (SOUND_MIXER_IMIX);
4203e2524b2eSHans Petter Selasky 
4204e2524b2eSHans Petter Selasky 	for (uat = uaudio_tt_to_feature; uat->terminal_type != 0; uat++) {
4205e2524b2eSHans Petter Selasky 		if (uat->terminal_type == terminal_type)
4206e2524b2eSHans Petter Selasky 			break;
4207e2524b2eSHans Petter Selasky 	}
4208e2524b2eSHans Petter Selasky 
4209e2524b2eSHans Petter Selasky 	DPRINTF("terminal_type=0x%04x -> %d\n",
42103a3f90c6SAndrew Thompson 	    terminal_type, uat->feature);
42113a3f90c6SAndrew Thompson 
42123a3f90c6SAndrew Thompson 	return (uat->feature);
42133a3f90c6SAndrew Thompson }
42143a3f90c6SAndrew Thompson 
42156d917491SHans Petter Selasky static const struct uaudio_terminal_node *
42166d917491SHans Petter Selasky uaudio_mixer_get_input(const struct uaudio_terminal_node *iot, uint8_t i)
42173a3f90c6SAndrew Thompson {
42183a3f90c6SAndrew Thompson 	struct uaudio_terminal_node *root = iot->root;
42193a3f90c6SAndrew Thompson 	uint8_t n;
42203a3f90c6SAndrew Thompson 
42213a3f90c6SAndrew Thompson 	n = iot->usr.id_max;
42223a3f90c6SAndrew Thompson 	do {
42233a3f90c6SAndrew Thompson 		if (iot->usr.bit_input[n / 8] & (1 << (n % 8))) {
42246d917491SHans Petter Selasky 			if (!i--)
42253a3f90c6SAndrew Thompson 				return (root + n);
42263a3f90c6SAndrew Thompson 		}
42273a3f90c6SAndrew Thompson 	} while (n--);
42283a3f90c6SAndrew Thompson 
42293a3f90c6SAndrew Thompson 	return (NULL);
42303a3f90c6SAndrew Thompson }
42313a3f90c6SAndrew Thompson 
42326d917491SHans Petter Selasky static const struct uaudio_terminal_node *
42336d917491SHans Petter Selasky uaudio_mixer_get_output(const struct uaudio_terminal_node *iot, uint8_t i)
42343a3f90c6SAndrew Thompson {
42353a3f90c6SAndrew Thompson 	struct uaudio_terminal_node *root = iot->root;
42363a3f90c6SAndrew Thompson 	uint8_t n;
42373a3f90c6SAndrew Thompson 
42383a3f90c6SAndrew Thompson 	n = iot->usr.id_max;
42393a3f90c6SAndrew Thompson 	do {
42403a3f90c6SAndrew Thompson 		if (iot->usr.bit_output[n / 8] & (1 << (n % 8))) {
42416d917491SHans Petter Selasky 			if (!i--)
42423a3f90c6SAndrew Thompson 				return (root + n);
42433a3f90c6SAndrew Thompson 		}
42443a3f90c6SAndrew Thompson 	} while (n--);
42453a3f90c6SAndrew Thompson 
42463a3f90c6SAndrew Thompson 	return (NULL);
42473a3f90c6SAndrew Thompson }
42483a3f90c6SAndrew Thompson 
42493a3f90c6SAndrew Thompson static void
42503a3f90c6SAndrew Thompson uaudio_mixer_find_inputs_sub(struct uaudio_terminal_node *root,
42513a3f90c6SAndrew Thompson     const uint8_t *p_id, uint8_t n_id,
42523a3f90c6SAndrew Thompson     struct uaudio_search_result *info)
42533a3f90c6SAndrew Thompson {
42543a3f90c6SAndrew Thompson 	struct uaudio_terminal_node *iot;
42553a3f90c6SAndrew Thompson 	uint8_t n;
42563a3f90c6SAndrew Thompson 	uint8_t i;
4257e2524b2eSHans Petter Selasky 	uint8_t is_last;
42583a3f90c6SAndrew Thompson 
4259e2524b2eSHans Petter Selasky top:
42603a3f90c6SAndrew Thompson 	for (n = 0; n < n_id; n++) {
42613a3f90c6SAndrew Thompson 
42623a3f90c6SAndrew Thompson 		i = p_id[n];
42633a3f90c6SAndrew Thompson 
4264e2524b2eSHans Petter Selasky 		if (info->recurse_level == UAUDIO_RECURSE_LIMIT) {
42653a3f90c6SAndrew Thompson 			DPRINTF("avoided going into a circle at id=%d!\n", i);
4266e2524b2eSHans Petter Selasky 			return;
42673a3f90c6SAndrew Thompson 		}
42683a3f90c6SAndrew Thompson 
4269e2524b2eSHans Petter Selasky 		info->recurse_level++;
4270e2524b2eSHans Petter Selasky 
42713a3f90c6SAndrew Thompson 		iot = (root + i);
42723a3f90c6SAndrew Thompson 
4273e2524b2eSHans Petter Selasky 		if (iot->u.desc == NULL)
42743a3f90c6SAndrew Thompson 			continue;
4275e2524b2eSHans Petter Selasky 
4276e2524b2eSHans Petter Selasky 		is_last = ((n + 1) == n_id);
4277e2524b2eSHans Petter Selasky 
42783a3f90c6SAndrew Thompson 		switch (iot->u.desc->bDescriptorSubtype) {
42793a3f90c6SAndrew Thompson 		case UDESCSUB_AC_INPUT:
42803a3f90c6SAndrew Thompson 			info->bit_input[i / 8] |= (1 << (i % 8));
42813a3f90c6SAndrew Thompson 			break;
42823a3f90c6SAndrew Thompson 
42833a3f90c6SAndrew Thompson 		case UDESCSUB_AC_FEATURE:
4284e2524b2eSHans Petter Selasky 			if (is_last) {
4285e2524b2eSHans Petter Selasky 				p_id = &iot->u.fu_v1->bSourceId;
4286e2524b2eSHans Petter Selasky 				n_id = 1;
4287e2524b2eSHans Petter Selasky 				goto top;
4288e2524b2eSHans Petter Selasky 			}
4289e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
4290e2524b2eSHans Petter Selasky 			    root, &iot->u.fu_v1->bSourceId, 1, info);
42913a3f90c6SAndrew Thompson 			break;
42923a3f90c6SAndrew Thompson 
42933a3f90c6SAndrew Thompson 		case UDESCSUB_AC_OUTPUT:
4294e2524b2eSHans Petter Selasky 			if (is_last) {
4295e2524b2eSHans Petter Selasky 				p_id = &iot->u.ot_v1->bSourceId;
4296e2524b2eSHans Petter Selasky 				n_id = 1;
4297e2524b2eSHans Petter Selasky 				goto top;
4298e2524b2eSHans Petter Selasky 			}
4299e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
4300e2524b2eSHans Petter Selasky 			    root, &iot->u.ot_v1->bSourceId, 1, info);
43013a3f90c6SAndrew Thompson 			break;
43023a3f90c6SAndrew Thompson 
43033a3f90c6SAndrew Thompson 		case UDESCSUB_AC_MIXER:
4304e2524b2eSHans Petter Selasky 			if (is_last) {
4305e2524b2eSHans Petter Selasky 				p_id = iot->u.mu_v1->baSourceId;
4306e2524b2eSHans Petter Selasky 				n_id = iot->u.mu_v1->bNrInPins;
4307e2524b2eSHans Petter Selasky 				goto top;
4308e2524b2eSHans Petter Selasky 			}
4309e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
4310e2524b2eSHans Petter Selasky 			    root, iot->u.mu_v1->baSourceId,
4311e2524b2eSHans Petter Selasky 			    iot->u.mu_v1->bNrInPins, info);
43123a3f90c6SAndrew Thompson 			break;
43133a3f90c6SAndrew Thompson 
43143a3f90c6SAndrew Thompson 		case UDESCSUB_AC_SELECTOR:
4315e2524b2eSHans Petter Selasky 			if (is_last) {
4316e2524b2eSHans Petter Selasky 				p_id = iot->u.su_v1->baSourceId;
4317e2524b2eSHans Petter Selasky 				n_id = iot->u.su_v1->bNrInPins;
4318e2524b2eSHans Petter Selasky 				goto top;
4319e2524b2eSHans Petter Selasky 			}
4320e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
4321e2524b2eSHans Petter Selasky 			    root, iot->u.su_v1->baSourceId,
4322e2524b2eSHans Petter Selasky 			    iot->u.su_v1->bNrInPins, info);
43233a3f90c6SAndrew Thompson 			break;
43243a3f90c6SAndrew Thompson 
43253a3f90c6SAndrew Thompson 		case UDESCSUB_AC_PROCESSING:
4326e2524b2eSHans Petter Selasky 			if (is_last) {
4327e2524b2eSHans Petter Selasky 				p_id = iot->u.pu_v1->baSourceId;
4328e2524b2eSHans Petter Selasky 				n_id = iot->u.pu_v1->bNrInPins;
4329e2524b2eSHans Petter Selasky 				goto top;
4330e2524b2eSHans Petter Selasky 			}
4331e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
4332e2524b2eSHans Petter Selasky 			    root, iot->u.pu_v1->baSourceId,
4333e2524b2eSHans Petter Selasky 			    iot->u.pu_v1->bNrInPins, info);
43343a3f90c6SAndrew Thompson 			break;
43353a3f90c6SAndrew Thompson 
43363a3f90c6SAndrew Thompson 		case UDESCSUB_AC_EXTENSION:
4337e2524b2eSHans Petter Selasky 			if (is_last) {
4338e2524b2eSHans Petter Selasky 				p_id = iot->u.eu_v1->baSourceId;
4339e2524b2eSHans Petter Selasky 				n_id = iot->u.eu_v1->bNrInPins;
4340e2524b2eSHans Petter Selasky 				goto top;
4341e2524b2eSHans Petter Selasky 			}
4342e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
4343e2524b2eSHans Petter Selasky 			    root, iot->u.eu_v1->baSourceId,
4344e2524b2eSHans Petter Selasky 			    iot->u.eu_v1->bNrInPins, info);
43453a3f90c6SAndrew Thompson 			break;
43463a3f90c6SAndrew Thompson 
43473a3f90c6SAndrew Thompson 		default:
43483a3f90c6SAndrew Thompson 			break;
43493a3f90c6SAndrew Thompson 		}
43503a3f90c6SAndrew Thompson 	}
4351e2524b2eSHans Petter Selasky }
4352e2524b2eSHans Petter Selasky 
4353e2524b2eSHans Petter Selasky static void
4354e2524b2eSHans Petter Selasky uaudio20_mixer_find_inputs_sub(struct uaudio_terminal_node *root,
4355e2524b2eSHans Petter Selasky     const uint8_t *p_id, uint8_t n_id,
4356e2524b2eSHans Petter Selasky     struct uaudio_search_result *info)
4357e2524b2eSHans Petter Selasky {
4358e2524b2eSHans Petter Selasky 	struct uaudio_terminal_node *iot;
4359e2524b2eSHans Petter Selasky 	uint8_t n;
4360e2524b2eSHans Petter Selasky 	uint8_t i;
4361e2524b2eSHans Petter Selasky 	uint8_t is_last;
4362e2524b2eSHans Petter Selasky 
4363e2524b2eSHans Petter Selasky top:
4364e2524b2eSHans Petter Selasky 	for (n = 0; n < n_id; n++) {
4365e2524b2eSHans Petter Selasky 
4366e2524b2eSHans Petter Selasky 		i = p_id[n];
4367e2524b2eSHans Petter Selasky 
4368e2524b2eSHans Petter Selasky 		if (info->recurse_level == UAUDIO_RECURSE_LIMIT) {
4369e2524b2eSHans Petter Selasky 			DPRINTF("avoided going into a circle at id=%d!\n", i);
4370e2524b2eSHans Petter Selasky 			return;
4371e2524b2eSHans Petter Selasky 		}
4372e2524b2eSHans Petter Selasky 
4373e2524b2eSHans Petter Selasky 		info->recurse_level++;
4374e2524b2eSHans Petter Selasky 
4375e2524b2eSHans Petter Selasky 		iot = (root + i);
4376e2524b2eSHans Petter Selasky 
4377e2524b2eSHans Petter Selasky 		if (iot->u.desc == NULL)
4378e2524b2eSHans Petter Selasky 			continue;
4379e2524b2eSHans Petter Selasky 
4380e2524b2eSHans Petter Selasky 		is_last = ((n + 1) == n_id);
4381e2524b2eSHans Petter Selasky 
4382e2524b2eSHans Petter Selasky 		switch (iot->u.desc->bDescriptorSubtype) {
4383e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_INPUT:
4384e2524b2eSHans Petter Selasky 			info->bit_input[i / 8] |= (1 << (i % 8));
4385e2524b2eSHans Petter Selasky 			break;
4386e2524b2eSHans Petter Selasky 
4387e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_OUTPUT:
4388e2524b2eSHans Petter Selasky 			if (is_last) {
4389e2524b2eSHans Petter Selasky 				p_id = &iot->u.ot_v2->bSourceId;
4390e2524b2eSHans Petter Selasky 				n_id = 1;
4391e2524b2eSHans Petter Selasky 				goto top;
4392e2524b2eSHans Petter Selasky 			}
4393e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4394e2524b2eSHans Petter Selasky 			    root, &iot->u.ot_v2->bSourceId, 1, info);
4395e2524b2eSHans Petter Selasky 			break;
4396e2524b2eSHans Petter Selasky 
4397e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_MIXER:
4398e2524b2eSHans Petter Selasky 			if (is_last) {
4399e2524b2eSHans Petter Selasky 				p_id = iot->u.mu_v2->baSourceId;
4400e2524b2eSHans Petter Selasky 				n_id = iot->u.mu_v2->bNrInPins;
4401e2524b2eSHans Petter Selasky 				goto top;
4402e2524b2eSHans Petter Selasky 			}
4403e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4404e2524b2eSHans Petter Selasky 			    root, iot->u.mu_v2->baSourceId,
4405e2524b2eSHans Petter Selasky 			    iot->u.mu_v2->bNrInPins, info);
4406e2524b2eSHans Petter Selasky 			break;
4407e2524b2eSHans Petter Selasky 
4408e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_SELECTOR:
4409e2524b2eSHans Petter Selasky 			if (is_last) {
4410e2524b2eSHans Petter Selasky 				p_id = iot->u.su_v2->baSourceId;
4411e2524b2eSHans Petter Selasky 				n_id = iot->u.su_v2->bNrInPins;
4412e2524b2eSHans Petter Selasky 				goto top;
4413e2524b2eSHans Petter Selasky 			}
4414e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4415e2524b2eSHans Petter Selasky 			    root, iot->u.su_v2->baSourceId,
4416e2524b2eSHans Petter Selasky 			    iot->u.su_v2->bNrInPins, info);
4417e2524b2eSHans Petter Selasky 			break;
4418e2524b2eSHans Petter Selasky 
4419e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_SAMPLE_RT:
4420e2524b2eSHans Petter Selasky 			if (is_last) {
4421e2524b2eSHans Petter Selasky 				p_id = &iot->u.ru_v2->bSourceId;
4422e2524b2eSHans Petter Selasky 				n_id = 1;
4423e2524b2eSHans Petter Selasky 				goto top;
4424e2524b2eSHans Petter Selasky 			}
4425e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4426e2524b2eSHans Petter Selasky 			    root, &iot->u.ru_v2->bSourceId,
4427e2524b2eSHans Petter Selasky 			    1, info);
4428e2524b2eSHans Petter Selasky 			break;
4429e2524b2eSHans Petter Selasky 
4430e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_EFFECT:
4431e2524b2eSHans Petter Selasky 			if (is_last) {
4432e2524b2eSHans Petter Selasky 				p_id = &iot->u.ef_v2->bSourceId;
4433e2524b2eSHans Petter Selasky 				n_id = 1;
4434e2524b2eSHans Petter Selasky 				goto top;
4435e2524b2eSHans Petter Selasky 			}
4436e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4437e2524b2eSHans Petter Selasky 			    root, &iot->u.ef_v2->bSourceId,
4438e2524b2eSHans Petter Selasky 			    1, info);
4439e2524b2eSHans Petter Selasky 			break;
4440e2524b2eSHans Petter Selasky 
4441e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_FEATURE:
4442e2524b2eSHans Petter Selasky 			if (is_last) {
4443e2524b2eSHans Petter Selasky 				p_id = &iot->u.fu_v2->bSourceId;
4444e2524b2eSHans Petter Selasky 				n_id = 1;
4445e2524b2eSHans Petter Selasky 				goto top;
4446e2524b2eSHans Petter Selasky 			}
4447e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4448e2524b2eSHans Petter Selasky 			    root, &iot->u.fu_v2->bSourceId, 1, info);
4449e2524b2eSHans Petter Selasky 			break;
4450e2524b2eSHans Petter Selasky 
4451e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_PROCESSING_V2:
4452e2524b2eSHans Petter Selasky 			if (is_last) {
4453e2524b2eSHans Petter Selasky 				p_id = iot->u.pu_v2->baSourceId;
4454e2524b2eSHans Petter Selasky 				n_id = iot->u.pu_v2->bNrInPins;
4455e2524b2eSHans Petter Selasky 				goto top;
4456e2524b2eSHans Petter Selasky 			}
4457e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4458e2524b2eSHans Petter Selasky 			    root, iot->u.pu_v2->baSourceId,
4459e2524b2eSHans Petter Selasky 			    iot->u.pu_v2->bNrInPins, info);
4460e2524b2eSHans Petter Selasky 			break;
4461e2524b2eSHans Petter Selasky 
4462e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_EXTENSION_V2:
4463e2524b2eSHans Petter Selasky 			if (is_last) {
4464e2524b2eSHans Petter Selasky 				p_id = iot->u.eu_v2->baSourceId;
4465e2524b2eSHans Petter Selasky 				n_id = iot->u.eu_v2->bNrInPins;
4466e2524b2eSHans Petter Selasky 				goto top;
4467e2524b2eSHans Petter Selasky 			}
4468e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4469e2524b2eSHans Petter Selasky 			    root, iot->u.eu_v2->baSourceId,
4470e2524b2eSHans Petter Selasky 			    iot->u.eu_v2->bNrInPins, info);
4471e2524b2eSHans Petter Selasky 			break;
4472e2524b2eSHans Petter Selasky 		default:
4473e2524b2eSHans Petter Selasky 			break;
4474e2524b2eSHans Petter Selasky 		}
4475e2524b2eSHans Petter Selasky 	}
4476e2524b2eSHans Petter Selasky }
4477e2524b2eSHans Petter Selasky 
4478e2524b2eSHans Petter Selasky static void
4479e2524b2eSHans Petter Selasky uaudio20_mixer_find_clocks_sub(struct uaudio_terminal_node *root,
4480e2524b2eSHans Petter Selasky     const uint8_t *p_id, uint8_t n_id,
4481e2524b2eSHans Petter Selasky     struct uaudio_search_result *info)
4482e2524b2eSHans Petter Selasky {
4483e2524b2eSHans Petter Selasky 	struct uaudio_terminal_node *iot;
4484e2524b2eSHans Petter Selasky 	uint8_t n;
4485e2524b2eSHans Petter Selasky 	uint8_t i;
4486e2524b2eSHans Petter Selasky 	uint8_t is_last;
4487e2524b2eSHans Petter Selasky 	uint8_t id;
4488e2524b2eSHans Petter Selasky 
4489e2524b2eSHans Petter Selasky top:
4490e2524b2eSHans Petter Selasky 	for (n = 0; n < n_id; n++) {
4491e2524b2eSHans Petter Selasky 
4492e2524b2eSHans Petter Selasky 		i = p_id[n];
4493e2524b2eSHans Petter Selasky 
4494e2524b2eSHans Petter Selasky 		if (info->recurse_level == UAUDIO_RECURSE_LIMIT) {
4495e2524b2eSHans Petter Selasky 			DPRINTF("avoided going into a circle at id=%d!\n", i);
4496e2524b2eSHans Petter Selasky 			return;
4497e2524b2eSHans Petter Selasky 		}
4498e2524b2eSHans Petter Selasky 
4499e2524b2eSHans Petter Selasky 		info->recurse_level++;
4500e2524b2eSHans Petter Selasky 
4501e2524b2eSHans Petter Selasky 		iot = (root + i);
4502e2524b2eSHans Petter Selasky 
4503e2524b2eSHans Petter Selasky 		if (iot->u.desc == NULL)
4504e2524b2eSHans Petter Selasky 			continue;
4505e2524b2eSHans Petter Selasky 
4506e2524b2eSHans Petter Selasky 		is_last = ((n + 1) == n_id);
4507e2524b2eSHans Petter Selasky 
4508e2524b2eSHans Petter Selasky 		switch (iot->u.desc->bDescriptorSubtype) {
4509e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_INPUT:
4510e2524b2eSHans Petter Selasky 			info->is_input = 1;
4511e2524b2eSHans Petter Selasky 			if (is_last) {
4512e2524b2eSHans Petter Selasky 				p_id = &iot->u.it_v2->bCSourceId;
4513e2524b2eSHans Petter Selasky 				n_id = 1;
4514e2524b2eSHans Petter Selasky 				goto top;
4515e2524b2eSHans Petter Selasky 			}
4516e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_clocks_sub(root,
4517e2524b2eSHans Petter Selasky 			    &iot->u.it_v2->bCSourceId, 1, info);
4518e2524b2eSHans Petter Selasky 			break;
4519e2524b2eSHans Petter Selasky 
4520e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_OUTPUT:
4521e2524b2eSHans Petter Selasky 			info->is_input = 0;
4522e2524b2eSHans Petter Selasky 			if (is_last) {
4523e2524b2eSHans Petter Selasky 				p_id = &iot->u.ot_v2->bCSourceId;
4524e2524b2eSHans Petter Selasky 				n_id = 1;
4525e2524b2eSHans Petter Selasky 				goto top;
4526e2524b2eSHans Petter Selasky 			}
4527e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_clocks_sub(root,
4528e2524b2eSHans Petter Selasky 			    &iot->u.ot_v2->bCSourceId, 1, info);
4529e2524b2eSHans Petter Selasky 			break;
4530e2524b2eSHans Petter Selasky 
4531e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_CLOCK_SEL:
4532e2524b2eSHans Petter Selasky 			if (is_last) {
4533e2524b2eSHans Petter Selasky 				p_id = iot->u.csel_v2->baCSourceId;
4534e2524b2eSHans Petter Selasky 				n_id = iot->u.csel_v2->bNrInPins;
4535e2524b2eSHans Petter Selasky 				goto top;
4536e2524b2eSHans Petter Selasky 			}
4537e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_clocks_sub(root,
4538e2524b2eSHans Petter Selasky 			    iot->u.csel_v2->baCSourceId,
4539e2524b2eSHans Petter Selasky 			    iot->u.csel_v2->bNrInPins, info);
4540e2524b2eSHans Petter Selasky 			break;
4541e2524b2eSHans Petter Selasky 
4542e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_CLOCK_MUL:
4543e2524b2eSHans Petter Selasky 			if (is_last) {
4544e2524b2eSHans Petter Selasky 				p_id = &iot->u.cmul_v2->bCSourceId;
4545e2524b2eSHans Petter Selasky 				n_id = 1;
4546e2524b2eSHans Petter Selasky 				goto top;
4547e2524b2eSHans Petter Selasky 			}
4548e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_clocks_sub(root,
4549e2524b2eSHans Petter Selasky 			    &iot->u.cmul_v2->bCSourceId,
4550e2524b2eSHans Petter Selasky 			    1, info);
4551e2524b2eSHans Petter Selasky 			break;
4552e2524b2eSHans Petter Selasky 
4553e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_CLOCK_SRC:
4554e2524b2eSHans Petter Selasky 
4555e2524b2eSHans Petter Selasky 			id = iot->u.csrc_v2->bClockId;
4556e2524b2eSHans Petter Selasky 
4557e2524b2eSHans Petter Selasky 			switch (info->is_input) {
4558e2524b2eSHans Petter Selasky 			case 0:
4559e2524b2eSHans Petter Selasky 				info->bit_output[id / 8] |= (1 << (id % 8));
4560e2524b2eSHans Petter Selasky 				break;
4561e2524b2eSHans Petter Selasky 			case 1:
4562e2524b2eSHans Petter Selasky 				info->bit_input[id / 8] |= (1 << (id % 8));
4563e2524b2eSHans Petter Selasky 				break;
4564e2524b2eSHans Petter Selasky 			default:
4565e2524b2eSHans Petter Selasky 				break;
4566e2524b2eSHans Petter Selasky 			}
4567e2524b2eSHans Petter Selasky 			break;
4568e2524b2eSHans Petter Selasky 
4569e2524b2eSHans Petter Selasky 		default:
4570e2524b2eSHans Petter Selasky 			break;
4571e2524b2eSHans Petter Selasky 		}
4572e2524b2eSHans Petter Selasky 	}
45733a3f90c6SAndrew Thompson }
45743a3f90c6SAndrew Thompson 
45753a3f90c6SAndrew Thompson static void
45763a3f90c6SAndrew Thompson uaudio_mixer_find_outputs_sub(struct uaudio_terminal_node *root, uint8_t id,
45773a3f90c6SAndrew Thompson     uint8_t n_id, struct uaudio_search_result *info)
45783a3f90c6SAndrew Thompson {
45793a3f90c6SAndrew Thompson 	struct uaudio_terminal_node *iot = (root + id);
45803a3f90c6SAndrew Thompson 	uint8_t j;
45813a3f90c6SAndrew Thompson 
45823a3f90c6SAndrew Thompson 	j = n_id;
45833a3f90c6SAndrew Thompson 	do {
45843a3f90c6SAndrew Thompson 		if ((j != id) && ((root + j)->u.desc) &&
45853a3f90c6SAndrew Thompson 		    ((root + j)->u.desc->bDescriptorSubtype == UDESCSUB_AC_OUTPUT)) {
45863a3f90c6SAndrew Thompson 
45873a3f90c6SAndrew Thompson 			/*
45883a3f90c6SAndrew Thompson 			 * "j" (output) <--- virtual wire <--- "id" (input)
45893a3f90c6SAndrew Thompson 			 *
45903a3f90c6SAndrew Thompson 			 * if "j" has "id" on the input, then "id" have "j" on
45913a3f90c6SAndrew Thompson 			 * the output, because they are connected:
45923a3f90c6SAndrew Thompson 			 */
45933a3f90c6SAndrew Thompson 			if ((root + j)->usr.bit_input[id / 8] & (1 << (id % 8))) {
45943a3f90c6SAndrew Thompson 				iot->usr.bit_output[j / 8] |= (1 << (j % 8));
45953a3f90c6SAndrew Thompson 			}
45963a3f90c6SAndrew Thompson 		}
45973a3f90c6SAndrew Thompson 	} while (j--);
45983a3f90c6SAndrew Thompson }
45993a3f90c6SAndrew Thompson 
46003a3f90c6SAndrew Thompson static void
4601e2524b2eSHans Petter Selasky uaudio_mixer_fill_info(struct uaudio_softc *sc,
4602e2524b2eSHans Petter Selasky     struct usb_device *udev, void *desc)
46033a3f90c6SAndrew Thompson {
46044c21be9bSRebecca Cran 	const struct usb_audio_control_descriptor *acdp;
4605a593f6b8SAndrew Thompson 	struct usb_config_descriptor *cd = usbd_get_config_descriptor(udev);
4606760bc48eSAndrew Thompson 	const struct usb_descriptor *dp;
46074c21be9bSRebecca Cran 	const struct usb_audio_unit *au;
46083a3f90c6SAndrew Thompson 	struct uaudio_terminal_node *iot = NULL;
46093a3f90c6SAndrew Thompson 	uint16_t wTotalLen;
46103a3f90c6SAndrew Thompson 	uint8_t ID_max = 0;		/* inclusive */
46113a3f90c6SAndrew Thompson 	uint8_t i;
46123a3f90c6SAndrew Thompson 
4613a593f6b8SAndrew Thompson 	desc = usb_desc_foreach(cd, desc);
46143a3f90c6SAndrew Thompson 
46153a3f90c6SAndrew Thompson 	if (desc == NULL) {
46163a3f90c6SAndrew Thompson 		DPRINTF("no Audio Control header\n");
46173a3f90c6SAndrew Thompson 		goto done;
46183a3f90c6SAndrew Thompson 	}
46193a3f90c6SAndrew Thompson 	acdp = desc;
46203a3f90c6SAndrew Thompson 
46213a3f90c6SAndrew Thompson 	if ((acdp->bLength < sizeof(*acdp)) ||
46223a3f90c6SAndrew Thompson 	    (acdp->bDescriptorType != UDESC_CS_INTERFACE) ||
46233a3f90c6SAndrew Thompson 	    (acdp->bDescriptorSubtype != UDESCSUB_AC_HEADER)) {
46243a3f90c6SAndrew Thompson 		DPRINTF("invalid Audio Control header\n");
46253a3f90c6SAndrew Thompson 		goto done;
46263a3f90c6SAndrew Thompson 	}
46273a3f90c6SAndrew Thompson 	/* "wTotalLen" is allowed to be corrupt */
46283a3f90c6SAndrew Thompson 	wTotalLen = UGETW(acdp->wTotalLength) - acdp->bLength;
46293a3f90c6SAndrew Thompson 
46303a3f90c6SAndrew Thompson 	/* get USB audio revision */
46313a3f90c6SAndrew Thompson 	sc->sc_audio_rev = UGETW(acdp->bcdADC);
46323a3f90c6SAndrew Thompson 
46333a3f90c6SAndrew Thompson 	DPRINTFN(3, "found AC header, vers=%03x, len=%d\n",
46343a3f90c6SAndrew Thompson 	    sc->sc_audio_rev, wTotalLen);
46353a3f90c6SAndrew Thompson 
46363a3f90c6SAndrew Thompson 	iot = malloc(sizeof(struct uaudio_terminal_node) * 256, M_TEMP,
46373a3f90c6SAndrew Thompson 	    M_WAITOK | M_ZERO);
46383a3f90c6SAndrew Thompson 
46393a3f90c6SAndrew Thompson 	if (iot == NULL) {
46403a3f90c6SAndrew Thompson 		DPRINTF("no memory!\n");
46413a3f90c6SAndrew Thompson 		goto done;
46423a3f90c6SAndrew Thompson 	}
4643a593f6b8SAndrew Thompson 	while ((desc = usb_desc_foreach(cd, desc))) {
46443a3f90c6SAndrew Thompson 
46453a3f90c6SAndrew Thompson 		dp = desc;
46463a3f90c6SAndrew Thompson 
46473a3f90c6SAndrew Thompson 		if (dp->bLength > wTotalLen) {
46483a3f90c6SAndrew Thompson 			break;
46493a3f90c6SAndrew Thompson 		} else {
46503a3f90c6SAndrew Thompson 			wTotalLen -= dp->bLength;
46513a3f90c6SAndrew Thompson 		}
46523a3f90c6SAndrew Thompson 
4653e2524b2eSHans Petter Selasky 		if (sc->sc_audio_rev >= UAUDIO_VERSION_30)
4654e2524b2eSHans Petter Selasky 			au = NULL;
4655e2524b2eSHans Petter Selasky 		else if (sc->sc_audio_rev >= UAUDIO_VERSION_20)
4656e2524b2eSHans Petter Selasky 			au = uaudio20_mixer_verify_desc(dp, 0);
4657e2524b2eSHans Petter Selasky 		else
46583a3f90c6SAndrew Thompson 			au = uaudio_mixer_verify_desc(dp, 0);
46593a3f90c6SAndrew Thompson 
46603a3f90c6SAndrew Thompson 		if (au) {
46613a3f90c6SAndrew Thompson 			iot[au->bUnitId].u.desc = (const void *)au;
4662e2524b2eSHans Petter Selasky 			if (au->bUnitId > ID_max)
46633a3f90c6SAndrew Thompson 				ID_max = au->bUnitId;
46643a3f90c6SAndrew Thompson 		}
46653a3f90c6SAndrew Thompson 	}
46663a3f90c6SAndrew Thompson 
46673a3f90c6SAndrew Thompson 	DPRINTF("Maximum ID=%d\n", ID_max);
46683a3f90c6SAndrew Thompson 
46693a3f90c6SAndrew Thompson 	/*
46703a3f90c6SAndrew Thompson 	 * determine sourcing inputs for
46713a3f90c6SAndrew Thompson 	 * all nodes in the tree:
46723a3f90c6SAndrew Thompson 	 */
46733a3f90c6SAndrew Thompson 	i = ID_max;
46743a3f90c6SAndrew Thompson 	do {
4675e2524b2eSHans Petter Selasky 		if (sc->sc_audio_rev >= UAUDIO_VERSION_30) {
4676e2524b2eSHans Petter Selasky 			/* FALLTHROUGH */
4677e2524b2eSHans Petter Selasky 		} else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) {
4678e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(iot,
4679e2524b2eSHans Petter Selasky 			    &i, 1, &((iot + i)->usr));
4680e2524b2eSHans Petter Selasky 
4681e2524b2eSHans Petter Selasky 			sc->sc_mixer_clocks.is_input = 255;
4682e2524b2eSHans Petter Selasky 			sc->sc_mixer_clocks.recurse_level = 0;
4683e2524b2eSHans Petter Selasky 
4684e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_clocks_sub(iot,
4685e2524b2eSHans Petter Selasky 			    &i, 1, &sc->sc_mixer_clocks);
4686e2524b2eSHans Petter Selasky 		} else {
4687e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(iot,
4688e2524b2eSHans Petter Selasky 			    &i, 1, &((iot + i)->usr));
4689e2524b2eSHans Petter Selasky 		}
46903a3f90c6SAndrew Thompson 	} while (i--);
46913a3f90c6SAndrew Thompson 
46923a3f90c6SAndrew Thompson 	/*
46933a3f90c6SAndrew Thompson 	 * determine outputs for
46943a3f90c6SAndrew Thompson 	 * all nodes in the tree:
46953a3f90c6SAndrew Thompson 	 */
46963a3f90c6SAndrew Thompson 	i = ID_max;
46973a3f90c6SAndrew Thompson 	do {
4698e2524b2eSHans Petter Selasky 		uaudio_mixer_find_outputs_sub(iot,
4699e2524b2eSHans Petter Selasky 		    i, ID_max, &((iot + i)->usr));
47003a3f90c6SAndrew Thompson 	} while (i--);
47013a3f90c6SAndrew Thompson 
47023a3f90c6SAndrew Thompson 	/* set "id_max" and "root" */
47033a3f90c6SAndrew Thompson 
47043a3f90c6SAndrew Thompson 	i = ID_max;
47053a3f90c6SAndrew Thompson 	do {
47063a3f90c6SAndrew Thompson 		(iot + i)->usr.id_max = ID_max;
47073a3f90c6SAndrew Thompson 		(iot + i)->root = iot;
47083a3f90c6SAndrew Thompson 	} while (i--);
47093a3f90c6SAndrew Thompson 
47103a3f90c6SAndrew Thompson 	/*
4711e2524b2eSHans Petter Selasky 	 * Scan the config to create a linked list of "mixer" nodes:
47123a3f90c6SAndrew Thompson 	 */
47133a3f90c6SAndrew Thompson 
47143a3f90c6SAndrew Thompson 	i = ID_max;
47153a3f90c6SAndrew Thompson 	do {
47163a3f90c6SAndrew Thompson 		dp = iot[i].u.desc;
47173a3f90c6SAndrew Thompson 
4718e2524b2eSHans Petter Selasky 		if (dp == NULL)
47193a3f90c6SAndrew Thompson 			continue;
4720e2524b2eSHans Petter Selasky 
47213a3f90c6SAndrew Thompson 		DPRINTFN(11, "id=%d subtype=%d\n",
47223a3f90c6SAndrew Thompson 		    i, dp->bDescriptorSubtype);
47233a3f90c6SAndrew Thompson 
4724e2524b2eSHans Petter Selasky 		if (sc->sc_audio_rev >= UAUDIO_VERSION_30) {
4725e2524b2eSHans Petter Selasky 			continue;
4726e2524b2eSHans Petter Selasky 		} else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) {
4727e2524b2eSHans Petter Selasky 
47283a3f90c6SAndrew Thompson 			switch (dp->bDescriptorSubtype) {
47293a3f90c6SAndrew Thompson 			case UDESCSUB_AC_HEADER:
47303a3f90c6SAndrew Thompson 				DPRINTF("unexpected AC header\n");
47313a3f90c6SAndrew Thompson 				break;
47323a3f90c6SAndrew Thompson 
47333a3f90c6SAndrew Thompson 			case UDESCSUB_AC_INPUT:
4734e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_OUTPUT:
4735e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_PROCESSING_V2:
4736e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_EXTENSION_V2:
4737e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_EFFECT:
4738e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_CLOCK_SRC:
4739e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_CLOCK_SEL:
4740e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_CLOCK_MUL:
4741e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_SAMPLE_RT:
47423a3f90c6SAndrew Thompson 				break;
47433a3f90c6SAndrew Thompson 
4744e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_MIXER:
4745e2524b2eSHans Petter Selasky 				uaudio20_mixer_add_mixer(sc, iot, i);
4746e2524b2eSHans Petter Selasky 				break;
4747e2524b2eSHans Petter Selasky 
4748e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_SELECTOR:
4749e2524b2eSHans Petter Selasky 				uaudio20_mixer_add_selector(sc, iot, i);
4750e2524b2eSHans Petter Selasky 				break;
4751e2524b2eSHans Petter Selasky 
4752e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_FEATURE:
4753e2524b2eSHans Petter Selasky 				uaudio20_mixer_add_feature(sc, iot, i);
4754e2524b2eSHans Petter Selasky 				break;
4755e2524b2eSHans Petter Selasky 
4756e2524b2eSHans Petter Selasky 			default:
4757e2524b2eSHans Petter Selasky 				DPRINTF("bad AC desc subtype=0x%02x\n",
4758e2524b2eSHans Petter Selasky 				    dp->bDescriptorSubtype);
4759e2524b2eSHans Petter Selasky 				break;
4760e2524b2eSHans Petter Selasky 			}
4761e2524b2eSHans Petter Selasky 			continue;
4762e2524b2eSHans Petter Selasky 		}
4763e2524b2eSHans Petter Selasky 
4764e2524b2eSHans Petter Selasky 		switch (dp->bDescriptorSubtype) {
4765e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_HEADER:
4766e2524b2eSHans Petter Selasky 			DPRINTF("unexpected AC header\n");
4767e2524b2eSHans Petter Selasky 			break;
4768e2524b2eSHans Petter Selasky 
4769e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_INPUT:
47703a3f90c6SAndrew Thompson 		case UDESCSUB_AC_OUTPUT:
47713a3f90c6SAndrew Thompson 			break;
47723a3f90c6SAndrew Thompson 
47733a3f90c6SAndrew Thompson 		case UDESCSUB_AC_MIXER:
47743a3f90c6SAndrew Thompson 			uaudio_mixer_add_mixer(sc, iot, i);
47753a3f90c6SAndrew Thompson 			break;
47763a3f90c6SAndrew Thompson 
47773a3f90c6SAndrew Thompson 		case UDESCSUB_AC_SELECTOR:
47783a3f90c6SAndrew Thompson 			uaudio_mixer_add_selector(sc, iot, i);
47793a3f90c6SAndrew Thompson 			break;
47803a3f90c6SAndrew Thompson 
47813a3f90c6SAndrew Thompson 		case UDESCSUB_AC_FEATURE:
47823a3f90c6SAndrew Thompson 			uaudio_mixer_add_feature(sc, iot, i);
47833a3f90c6SAndrew Thompson 			break;
47843a3f90c6SAndrew Thompson 
47853a3f90c6SAndrew Thompson 		case UDESCSUB_AC_PROCESSING:
47863a3f90c6SAndrew Thompson 			uaudio_mixer_add_processing(sc, iot, i);
47873a3f90c6SAndrew Thompson 			break;
47883a3f90c6SAndrew Thompson 
47893a3f90c6SAndrew Thompson 		case UDESCSUB_AC_EXTENSION:
47903a3f90c6SAndrew Thompson 			uaudio_mixer_add_extension(sc, iot, i);
47913a3f90c6SAndrew Thompson 			break;
47923a3f90c6SAndrew Thompson 
47933a3f90c6SAndrew Thompson 		default:
47943a3f90c6SAndrew Thompson 			DPRINTF("bad AC desc subtype=0x%02x\n",
47953a3f90c6SAndrew Thompson 			    dp->bDescriptorSubtype);
47963a3f90c6SAndrew Thompson 			break;
47973a3f90c6SAndrew Thompson 		}
47983a3f90c6SAndrew Thompson 
47993a3f90c6SAndrew Thompson 	} while (i--);
48003a3f90c6SAndrew Thompson 
48013a3f90c6SAndrew Thompson done:
48023a3f90c6SAndrew Thompson 	free(iot, M_TEMP);
48033a3f90c6SAndrew Thompson }
48043a3f90c6SAndrew Thompson 
4805e2524b2eSHans Petter Selasky static int
4806e2524b2eSHans Petter Selasky uaudio_mixer_get(struct usb_device *udev, uint16_t audio_rev,
4807e2524b2eSHans Petter Selasky     uint8_t what, struct uaudio_mixer_node *mc)
48083a3f90c6SAndrew Thompson {
4809760bc48eSAndrew Thompson 	struct usb_device_request req;
4810e2524b2eSHans Petter Selasky 	int val;
4811e2524b2eSHans Petter Selasky 	uint8_t data[2 + (2 * 3)];
4812e0a69b51SAndrew Thompson 	usb_error_t err;
48133a3f90c6SAndrew Thompson 
4814e2524b2eSHans Petter Selasky 	if (mc->wValue[0] == -1)
48153a3f90c6SAndrew Thompson 		return (0);
4816e2524b2eSHans Petter Selasky 
4817e2524b2eSHans Petter Selasky 	if (audio_rev >= UAUDIO_VERSION_30)
4818e2524b2eSHans Petter Selasky 		return (0);
4819e2524b2eSHans Petter Selasky 	else if (audio_rev >= UAUDIO_VERSION_20) {
4820e2524b2eSHans Petter Selasky 		if (what == GET_CUR) {
4821e2524b2eSHans Petter Selasky 			req.bRequest = UA20_CS_CUR;
4822e2524b2eSHans Petter Selasky 			USETW(req.wLength, 2);
4823e2524b2eSHans Petter Selasky 		} else {
4824e2524b2eSHans Petter Selasky 			req.bRequest = UA20_CS_RANGE;
4825e2524b2eSHans Petter Selasky 			USETW(req.wLength, 8);
48263a3f90c6SAndrew Thompson 		}
4827e2524b2eSHans Petter Selasky 	} else {
4828e2524b2eSHans Petter Selasky 		uint16_t len = MIX_SIZE(mc->type);
4829e2524b2eSHans Petter Selasky 
48303a3f90c6SAndrew Thompson 		req.bRequest = what;
4831e2524b2eSHans Petter Selasky 		USETW(req.wLength, len);
4832e2524b2eSHans Petter Selasky 	}
4833e2524b2eSHans Petter Selasky 
4834e2524b2eSHans Petter Selasky 	req.bmRequestType = UT_READ_CLASS_INTERFACE;
48353a3f90c6SAndrew Thompson 	USETW(req.wValue, mc->wValue[0]);
48363a3f90c6SAndrew Thompson 	USETW(req.wIndex, mc->wIndex);
4837e2524b2eSHans Petter Selasky 
4838e2524b2eSHans Petter Selasky 	memset(data, 0, sizeof(data));
48393a3f90c6SAndrew Thompson 
4840f24b6817SAlfred Perlstein 	err = usbd_do_request(udev, NULL, &req, data);
48413a3f90c6SAndrew Thompson 	if (err) {
4842a593f6b8SAndrew Thompson 		DPRINTF("err=%s\n", usbd_errstr(err));
48433a3f90c6SAndrew Thompson 		return (0);
48443a3f90c6SAndrew Thompson 	}
4845e2524b2eSHans Petter Selasky 
4846e2524b2eSHans Petter Selasky 	if (audio_rev >= UAUDIO_VERSION_30) {
4847e2524b2eSHans Petter Selasky 		val = 0;
4848e2524b2eSHans Petter Selasky 	} else if (audio_rev >= UAUDIO_VERSION_20) {
4849e2524b2eSHans Petter Selasky 		switch (what) {
4850e2524b2eSHans Petter Selasky 		case GET_CUR:
48513a3f90c6SAndrew Thompson 			val = (data[0] | (data[1] << 8));
4852e2524b2eSHans Petter Selasky 			break;
4853e2524b2eSHans Petter Selasky 		case GET_MIN:
4854e2524b2eSHans Petter Selasky 			val = (data[2] | (data[3] << 8));
4855e2524b2eSHans Petter Selasky 			break;
4856e2524b2eSHans Petter Selasky 		case GET_MAX:
4857e2524b2eSHans Petter Selasky 			val = (data[4] | (data[5] << 8));
4858e2524b2eSHans Petter Selasky 			break;
4859e2524b2eSHans Petter Selasky 		case GET_RES:
4860e2524b2eSHans Petter Selasky 			val = (data[6] | (data[7] << 8));
4861e2524b2eSHans Petter Selasky 			break;
4862e2524b2eSHans Petter Selasky 		default:
4863e2524b2eSHans Petter Selasky 			val = 0;
4864e2524b2eSHans Petter Selasky 			break;
4865e2524b2eSHans Petter Selasky 		}
4866e2524b2eSHans Petter Selasky 	} else {
4867e2524b2eSHans Petter Selasky 		val = (data[0] | (data[1] << 8));
4868e2524b2eSHans Petter Selasky 	}
4869e2524b2eSHans Petter Selasky 
4870e2524b2eSHans Petter Selasky 	if (what == GET_CUR || what == GET_MIN || what == GET_MAX)
4871e2524b2eSHans Petter Selasky 		val = uaudio_mixer_signext(mc->type, val);
48723a3f90c6SAndrew Thompson 
48733a3f90c6SAndrew Thompson 	DPRINTFN(3, "val=%d\n", val);
48743a3f90c6SAndrew Thompson 
48753a3f90c6SAndrew Thompson 	return (val);
48763a3f90c6SAndrew Thompson }
48773a3f90c6SAndrew Thompson 
48783a3f90c6SAndrew Thompson static void
4879ed6d949aSAndrew Thompson uaudio_mixer_write_cfg_callback(struct usb_xfer *xfer, usb_error_t error)
48803a3f90c6SAndrew Thompson {
4881760bc48eSAndrew Thompson 	struct usb_device_request req;
4882ed6d949aSAndrew Thompson 	struct uaudio_softc *sc = usbd_xfer_softc(xfer);
48833a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *mc = sc->sc_mixer_curr;
4884ed6d949aSAndrew Thompson 	struct usb_page_cache *pc;
48853a3f90c6SAndrew Thompson 	uint16_t len;
48863a3f90c6SAndrew Thompson 	uint8_t repeat = 1;
48873a3f90c6SAndrew Thompson 	uint8_t update;
48883a3f90c6SAndrew Thompson 	uint8_t chan;
48893a3f90c6SAndrew Thompson 	uint8_t buf[2];
48903a3f90c6SAndrew Thompson 
48913a3f90c6SAndrew Thompson 	DPRINTF("\n");
48923a3f90c6SAndrew Thompson 
48933a3f90c6SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
48943a3f90c6SAndrew Thompson 	case USB_ST_TRANSFERRED:
48953a3f90c6SAndrew Thompson tr_transferred:
48963a3f90c6SAndrew Thompson 	case USB_ST_SETUP:
48973a3f90c6SAndrew Thompson tr_setup:
48983a3f90c6SAndrew Thompson 
48993a3f90c6SAndrew Thompson 		if (mc == NULL) {
49003a3f90c6SAndrew Thompson 			mc = sc->sc_mixer_root;
49013a3f90c6SAndrew Thompson 			sc->sc_mixer_curr = mc;
49023a3f90c6SAndrew Thompson 			sc->sc_mixer_chan = 0;
49033a3f90c6SAndrew Thompson 			repeat = 0;
49043a3f90c6SAndrew Thompson 		}
49053a3f90c6SAndrew Thompson 		while (mc) {
49063a3f90c6SAndrew Thompson 			while (sc->sc_mixer_chan < mc->nchan) {
49073a3f90c6SAndrew Thompson 
49083a3f90c6SAndrew Thompson 				chan = sc->sc_mixer_chan;
49093a3f90c6SAndrew Thompson 
49103a3f90c6SAndrew Thompson 				sc->sc_mixer_chan++;
49113a3f90c6SAndrew Thompson 
49123a3f90c6SAndrew Thompson 				update = ((mc->update[chan / 8] & (1 << (chan % 8))) &&
49133a3f90c6SAndrew Thompson 				    (mc->wValue[chan] != -1));
49143a3f90c6SAndrew Thompson 
49153a3f90c6SAndrew Thompson 				mc->update[chan / 8] &= ~(1 << (chan % 8));
49163a3f90c6SAndrew Thompson 
49173a3f90c6SAndrew Thompson 				if (update) {
49183a3f90c6SAndrew Thompson 
49193a3f90c6SAndrew Thompson 					req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
49203a3f90c6SAndrew Thompson 					USETW(req.wValue, mc->wValue[chan]);
49213a3f90c6SAndrew Thompson 					USETW(req.wIndex, mc->wIndex);
49223a3f90c6SAndrew Thompson 
4923e2524b2eSHans Petter Selasky 					if (sc->sc_audio_rev >= UAUDIO_VERSION_30) {
4924e2524b2eSHans Petter Selasky 						return;
4925e2524b2eSHans Petter Selasky 					} else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) {
4926e2524b2eSHans Petter Selasky 						len = 2;
4927e2524b2eSHans Petter Selasky 						req.bRequest = UA20_CS_CUR;
4928e2524b2eSHans Petter Selasky 						USETW(req.wLength, len);
4929e2524b2eSHans Petter Selasky 					} else {
4930e2524b2eSHans Petter Selasky 						len = MIX_SIZE(mc->type);
4931e2524b2eSHans Petter Selasky 						req.bRequest = SET_CUR;
4932e2524b2eSHans Petter Selasky 						USETW(req.wLength, len);
4933e2524b2eSHans Petter Selasky 					}
4934e2524b2eSHans Petter Selasky 
49353a3f90c6SAndrew Thompson 					buf[0] = (mc->wData[chan] & 0xFF);
49363a3f90c6SAndrew Thompson 					buf[1] = (mc->wData[chan] >> 8) & 0xFF;
4937e2524b2eSHans Petter Selasky 
4938ed6d949aSAndrew Thompson 					pc = usbd_xfer_get_frame(xfer, 0);
4939ed6d949aSAndrew Thompson 					usbd_copy_in(pc, 0, &req, sizeof(req));
4940ed6d949aSAndrew Thompson 					pc = usbd_xfer_get_frame(xfer, 1);
4941ed6d949aSAndrew Thompson 					usbd_copy_in(pc, 0, buf, len);
49423a3f90c6SAndrew Thompson 
4943ed6d949aSAndrew Thompson 					usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
4944ed6d949aSAndrew Thompson 					usbd_xfer_set_frame_len(xfer, 1, len);
4945ed6d949aSAndrew Thompson 					usbd_xfer_set_frames(xfer, len ? 2 : 1);
4946a593f6b8SAndrew Thompson 					usbd_transfer_submit(xfer);
49473a3f90c6SAndrew Thompson 					return;
49483a3f90c6SAndrew Thompson 				}
49493a3f90c6SAndrew Thompson 			}
49503a3f90c6SAndrew Thompson 
49513a3f90c6SAndrew Thompson 			mc = mc->next;
49523a3f90c6SAndrew Thompson 			sc->sc_mixer_curr = mc;
49533a3f90c6SAndrew Thompson 			sc->sc_mixer_chan = 0;
49543a3f90c6SAndrew Thompson 		}
49553a3f90c6SAndrew Thompson 
49563a3f90c6SAndrew Thompson 		if (repeat) {
49573a3f90c6SAndrew Thompson 			goto tr_setup;
49583a3f90c6SAndrew Thompson 		}
49593a3f90c6SAndrew Thompson 		break;
49603a3f90c6SAndrew Thompson 
49613a3f90c6SAndrew Thompson 	default:			/* Error */
4962ed6d949aSAndrew Thompson 		DPRINTF("error=%s\n", usbd_errstr(error));
4963ed6d949aSAndrew Thompson 		if (error == USB_ERR_CANCELLED) {
49643a3f90c6SAndrew Thompson 			/* do nothing - we are detaching */
49653a3f90c6SAndrew Thompson 			break;
49663a3f90c6SAndrew Thompson 		}
49673a3f90c6SAndrew Thompson 		goto tr_transferred;
49683a3f90c6SAndrew Thompson 	}
49693a3f90c6SAndrew Thompson }
49703a3f90c6SAndrew Thompson 
4971e0a69b51SAndrew Thompson static usb_error_t
4972760bc48eSAndrew Thompson uaudio_set_speed(struct usb_device *udev, uint8_t endpt, uint32_t speed)
49733a3f90c6SAndrew Thompson {
4974760bc48eSAndrew Thompson 	struct usb_device_request req;
49753a3f90c6SAndrew Thompson 	uint8_t data[3];
49763a3f90c6SAndrew Thompson 
49773a3f90c6SAndrew Thompson 	DPRINTFN(6, "endpt=%d speed=%u\n", endpt, speed);
49783a3f90c6SAndrew Thompson 
49793a3f90c6SAndrew Thompson 	req.bmRequestType = UT_WRITE_CLASS_ENDPOINT;
49803a3f90c6SAndrew Thompson 	req.bRequest = SET_CUR;
49813a3f90c6SAndrew Thompson 	USETW2(req.wValue, SAMPLING_FREQ_CONTROL, 0);
49823a3f90c6SAndrew Thompson 	USETW(req.wIndex, endpt);
49833a3f90c6SAndrew Thompson 	USETW(req.wLength, 3);
49843a3f90c6SAndrew Thompson 	data[0] = speed;
49853a3f90c6SAndrew Thompson 	data[1] = speed >> 8;
49863a3f90c6SAndrew Thompson 	data[2] = speed >> 16;
49873a3f90c6SAndrew Thompson 
4988f24b6817SAlfred Perlstein 	return (usbd_do_request(udev, NULL, &req, data));
49893a3f90c6SAndrew Thompson }
49903a3f90c6SAndrew Thompson 
4991e2524b2eSHans Petter Selasky static usb_error_t
4992e2524b2eSHans Petter Selasky uaudio20_set_speed(struct usb_device *udev, uint8_t iface_no,
4993e2524b2eSHans Petter Selasky     uint8_t clockid, uint32_t speed)
4994e2524b2eSHans Petter Selasky {
4995e2524b2eSHans Petter Selasky 	struct usb_device_request req;
4996e2524b2eSHans Petter Selasky 	uint8_t data[4];
4997e2524b2eSHans Petter Selasky 
4998e2524b2eSHans Petter Selasky 	DPRINTFN(6, "ifaceno=%d clockid=%d speed=%u\n",
4999e2524b2eSHans Petter Selasky 	    iface_no, clockid, speed);
5000e2524b2eSHans Petter Selasky 
5001e2524b2eSHans Petter Selasky 	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
5002e2524b2eSHans Petter Selasky 	req.bRequest = UA20_CS_CUR;
5003e2524b2eSHans Petter Selasky 	USETW2(req.wValue, UA20_CS_SAM_FREQ_CONTROL, 0);
5004e2524b2eSHans Petter Selasky 	USETW2(req.wIndex, clockid, iface_no);
5005e2524b2eSHans Petter Selasky 	USETW(req.wLength, 4);
5006e2524b2eSHans Petter Selasky 	data[0] = speed;
5007e2524b2eSHans Petter Selasky 	data[1] = speed >> 8;
5008e2524b2eSHans Petter Selasky 	data[2] = speed >> 16;
5009e2524b2eSHans Petter Selasky 	data[3] = speed >> 24;
5010e2524b2eSHans Petter Selasky 
5011e2524b2eSHans Petter Selasky 	return (usbd_do_request(udev, NULL, &req, data));
5012e2524b2eSHans Petter Selasky }
5013e2524b2eSHans Petter Selasky 
50143a3f90c6SAndrew Thompson static int
50153a3f90c6SAndrew Thompson uaudio_mixer_signext(uint8_t type, int val)
50163a3f90c6SAndrew Thompson {
50173a3f90c6SAndrew Thompson 	if (!MIX_UNSIGNED(type)) {
50183a3f90c6SAndrew Thompson 		if (MIX_SIZE(type) == 2) {
50193a3f90c6SAndrew Thompson 			val = (int16_t)val;
50203a3f90c6SAndrew Thompson 		} else {
50213a3f90c6SAndrew Thompson 			val = (int8_t)val;
50223a3f90c6SAndrew Thompson 		}
50233a3f90c6SAndrew Thompson 	}
50243a3f90c6SAndrew Thompson 	return (val);
50253a3f90c6SAndrew Thompson }
50263a3f90c6SAndrew Thompson 
50273a3f90c6SAndrew Thompson static int
50283a3f90c6SAndrew Thompson uaudio_mixer_bsd2value(struct uaudio_mixer_node *mc, int32_t val)
50293a3f90c6SAndrew Thompson {
50303a3f90c6SAndrew Thompson 	if (mc->type == MIX_ON_OFF) {
50313a3f90c6SAndrew Thompson 		val = (val != 0);
50323a3f90c6SAndrew Thompson 	} else if (mc->type == MIX_SELECTOR) {
50333a3f90c6SAndrew Thompson 		if ((val < mc->minval) ||
50343a3f90c6SAndrew Thompson 		    (val > mc->maxval)) {
50353a3f90c6SAndrew Thompson 			val = mc->minval;
50363a3f90c6SAndrew Thompson 		}
50373a3f90c6SAndrew Thompson 	} else {
5038b029f6bbSAndrew Thompson 
5039b029f6bbSAndrew Thompson 		/* compute actual volume */
5040b029f6bbSAndrew Thompson 		val = (val * mc->mul) / 255;
5041b029f6bbSAndrew Thompson 
5042b029f6bbSAndrew Thompson 		/* add lower offset */
5043b029f6bbSAndrew Thompson 		val = val + mc->minval;
5044b029f6bbSAndrew Thompson 
5045b029f6bbSAndrew Thompson 		/* make sure we don't write a value out of range */
5046b029f6bbSAndrew Thompson 		if (val > mc->maxval)
5047b029f6bbSAndrew Thompson 			val = mc->maxval;
5048b029f6bbSAndrew Thompson 		else if (val < mc->minval)
5049b029f6bbSAndrew Thompson 			val = mc->minval;
50503a3f90c6SAndrew Thompson 	}
50513a3f90c6SAndrew Thompson 
50523a3f90c6SAndrew Thompson 	DPRINTFN(6, "type=0x%03x val=%d min=%d max=%d val=%d\n",
50533a3f90c6SAndrew Thompson 	    mc->type, val, mc->minval, mc->maxval, val);
50543a3f90c6SAndrew Thompson 	return (val);
50553a3f90c6SAndrew Thompson }
50563a3f90c6SAndrew Thompson 
50573a3f90c6SAndrew Thompson static void
50583a3f90c6SAndrew Thompson uaudio_mixer_ctl_set(struct uaudio_softc *sc, struct uaudio_mixer_node *mc,
50593a3f90c6SAndrew Thompson     uint8_t chan, int32_t val)
50603a3f90c6SAndrew Thompson {
50613a3f90c6SAndrew Thompson 	val = uaudio_mixer_bsd2value(mc, val);
50623a3f90c6SAndrew Thompson 
50633a3f90c6SAndrew Thompson 	mc->update[chan / 8] |= (1 << (chan % 8));
50643a3f90c6SAndrew Thompson 	mc->wData[chan] = val;
50653a3f90c6SAndrew Thompson 
50663a3f90c6SAndrew Thompson 	/* start the transfer, if not already started */
50673a3f90c6SAndrew Thompson 
5068a593f6b8SAndrew Thompson 	usbd_transfer_start(sc->sc_mixer_xfer[0]);
50693a3f90c6SAndrew Thompson }
50703a3f90c6SAndrew Thompson 
50713a3f90c6SAndrew Thompson static void
50723a3f90c6SAndrew Thompson uaudio_mixer_init(struct uaudio_softc *sc)
50733a3f90c6SAndrew Thompson {
50743a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *mc;
50753a3f90c6SAndrew Thompson 	int32_t i;
50763a3f90c6SAndrew Thompson 
50773a3f90c6SAndrew Thompson 	for (mc = sc->sc_mixer_root; mc;
50783a3f90c6SAndrew Thompson 	    mc = mc->next) {
50793a3f90c6SAndrew Thompson 
50803a3f90c6SAndrew Thompson 		if (mc->ctl != SOUND_MIXER_NRDEVICES) {
50813a3f90c6SAndrew Thompson 			/*
50823a3f90c6SAndrew Thompson 			 * Set device mask bits. See
50833a3f90c6SAndrew Thompson 			 * /usr/include/machine/soundcard.h
50843a3f90c6SAndrew Thompson 			 */
50853a3f90c6SAndrew Thompson 			sc->sc_mix_info |= (1 << mc->ctl);
50863a3f90c6SAndrew Thompson 		}
50873a3f90c6SAndrew Thompson 		if ((mc->ctl == SOUND_MIXER_NRDEVICES) &&
50883a3f90c6SAndrew Thompson 		    (mc->type == MIX_SELECTOR)) {
50893a3f90c6SAndrew Thompson 
50903a3f90c6SAndrew Thompson 			for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) {
50913a3f90c6SAndrew Thompson 				if (mc->slctrtype[i - 1] == SOUND_MIXER_NRDEVICES) {
50923a3f90c6SAndrew Thompson 					continue;
50933a3f90c6SAndrew Thompson 				}
50943a3f90c6SAndrew Thompson 				sc->sc_recsrc_info |= 1 << mc->slctrtype[i - 1];
50953a3f90c6SAndrew Thompson 			}
50963a3f90c6SAndrew Thompson 		}
50973a3f90c6SAndrew Thompson 	}
50983a3f90c6SAndrew Thompson }
50993a3f90c6SAndrew Thompson 
51003a3f90c6SAndrew Thompson int
51013a3f90c6SAndrew Thompson uaudio_mixer_init_sub(struct uaudio_softc *sc, struct snd_mixer *m)
51023a3f90c6SAndrew Thompson {
51033a3f90c6SAndrew Thompson 	DPRINTF("\n");
51043a3f90c6SAndrew Thompson 
5105902514f6SHans Petter Selasky 	sc->sc_mixer_lock = mixer_get_lock(m);
510676b71212SHans Petter Selasky 	sc->sc_mixer_dev = m;
5107902514f6SHans Petter Selasky 
5108a593f6b8SAndrew Thompson 	if (usbd_transfer_setup(sc->sc_udev, &sc->sc_mixer_iface_index,
51093a3f90c6SAndrew Thompson 	    sc->sc_mixer_xfer, uaudio_mixer_config, 1, sc,
5110902514f6SHans Petter Selasky 	    sc->sc_mixer_lock)) {
51113a3f90c6SAndrew Thompson 		DPRINTFN(0, "could not allocate USB "
51123a3f90c6SAndrew Thompson 		    "transfer for audio mixer!\n");
51133a3f90c6SAndrew Thompson 		return (ENOMEM);
51143a3f90c6SAndrew Thompson 	}
51153a3f90c6SAndrew Thompson 	if (!(sc->sc_mix_info & SOUND_MASK_VOLUME)) {
51163a3f90c6SAndrew Thompson 		mix_setparentchild(m, SOUND_MIXER_VOLUME, SOUND_MASK_PCM);
51173a3f90c6SAndrew Thompson 		mix_setrealdev(m, SOUND_MIXER_VOLUME, SOUND_MIXER_NONE);
51183a3f90c6SAndrew Thompson 	}
51193a3f90c6SAndrew Thompson 	mix_setdevs(m, sc->sc_mix_info);
51203a3f90c6SAndrew Thompson 	mix_setrecdevs(m, sc->sc_recsrc_info);
51213a3f90c6SAndrew Thompson 	return (0);
51223a3f90c6SAndrew Thompson }
51233a3f90c6SAndrew Thompson 
51243a3f90c6SAndrew Thompson int
51253a3f90c6SAndrew Thompson uaudio_mixer_uninit_sub(struct uaudio_softc *sc)
51263a3f90c6SAndrew Thompson {
51273a3f90c6SAndrew Thompson 	DPRINTF("\n");
51283a3f90c6SAndrew Thompson 
5129a593f6b8SAndrew Thompson 	usbd_transfer_unsetup(sc->sc_mixer_xfer, 1);
51303a3f90c6SAndrew Thompson 
5131902514f6SHans Petter Selasky 	sc->sc_mixer_lock = NULL;
5132902514f6SHans Petter Selasky 
51333a3f90c6SAndrew Thompson 	return (0);
51343a3f90c6SAndrew Thompson }
51353a3f90c6SAndrew Thompson 
51363a3f90c6SAndrew Thompson void
51373a3f90c6SAndrew Thompson uaudio_mixer_set(struct uaudio_softc *sc, unsigned type,
51383a3f90c6SAndrew Thompson     unsigned left, unsigned right)
51393a3f90c6SAndrew Thompson {
51403a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *mc;
5141902514f6SHans Petter Selasky 	int chan;
51423a3f90c6SAndrew Thompson 
5143902514f6SHans Petter Selasky 	for (mc = sc->sc_mixer_root; mc != NULL; mc = mc->next) {
51443a3f90c6SAndrew Thompson 
51453a3f90c6SAndrew Thompson 		if (mc->ctl == type) {
5146902514f6SHans Petter Selasky 			for (chan = 0; chan < mc->nchan; chan++) {
5147902514f6SHans Petter Selasky 				uaudio_mixer_ctl_set(sc, mc, chan,
5148902514f6SHans Petter Selasky 				    (int)((chan == 0 ? left : right) *
5149902514f6SHans Petter Selasky 				    255) / 100);
51503a3f90c6SAndrew Thompson 			}
51513a3f90c6SAndrew Thompson 		}
51523a3f90c6SAndrew Thompson 	}
51533a3f90c6SAndrew Thompson }
51543a3f90c6SAndrew Thompson 
51553a3f90c6SAndrew Thompson uint32_t
51563a3f90c6SAndrew Thompson uaudio_mixer_setrecsrc(struct uaudio_softc *sc, uint32_t src)
51573a3f90c6SAndrew Thompson {
51583a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *mc;
51593a3f90c6SAndrew Thompson 	uint32_t mask;
51603a3f90c6SAndrew Thompson 	uint32_t temp;
51613a3f90c6SAndrew Thompson 	int32_t i;
51623a3f90c6SAndrew Thompson 
51633a3f90c6SAndrew Thompson 	for (mc = sc->sc_mixer_root; mc;
51643a3f90c6SAndrew Thompson 	    mc = mc->next) {
51653a3f90c6SAndrew Thompson 
51663a3f90c6SAndrew Thompson 		if ((mc->ctl == SOUND_MIXER_NRDEVICES) &&
51673a3f90c6SAndrew Thompson 		    (mc->type == MIX_SELECTOR)) {
51683a3f90c6SAndrew Thompson 
51693a3f90c6SAndrew Thompson 			/* compute selector mask */
51703a3f90c6SAndrew Thompson 
51713a3f90c6SAndrew Thompson 			mask = 0;
51723a3f90c6SAndrew Thompson 			for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) {
51733a3f90c6SAndrew Thompson 				mask |= (1 << mc->slctrtype[i - 1]);
51743a3f90c6SAndrew Thompson 			}
51753a3f90c6SAndrew Thompson 
51763a3f90c6SAndrew Thompson 			temp = mask & src;
51773a3f90c6SAndrew Thompson 			if (temp == 0) {
51783a3f90c6SAndrew Thompson 				continue;
51793a3f90c6SAndrew Thompson 			}
51803a3f90c6SAndrew Thompson 			/* find the first set bit */
51813a3f90c6SAndrew Thompson 			temp = (-temp) & temp;
51823a3f90c6SAndrew Thompson 
51833a3f90c6SAndrew Thompson 			/* update "src" */
51843a3f90c6SAndrew Thompson 			src &= ~mask;
51853a3f90c6SAndrew Thompson 			src |= temp;
51863a3f90c6SAndrew Thompson 
51873a3f90c6SAndrew Thompson 			for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) {
51883a3f90c6SAndrew Thompson 				if (temp != (1 << mc->slctrtype[i - 1])) {
51893a3f90c6SAndrew Thompson 					continue;
51903a3f90c6SAndrew Thompson 				}
51913a3f90c6SAndrew Thompson 				uaudio_mixer_ctl_set(sc, mc, 0, i);
51923a3f90c6SAndrew Thompson 				break;
51933a3f90c6SAndrew Thompson 			}
51943a3f90c6SAndrew Thompson 		}
51953a3f90c6SAndrew Thompson 	}
51963a3f90c6SAndrew Thompson 	return (src);
51973a3f90c6SAndrew Thompson }
51983a3f90c6SAndrew Thompson 
51993a3f90c6SAndrew Thompson /*========================================================================*
52003a3f90c6SAndrew Thompson  * MIDI support routines
52013a3f90c6SAndrew Thompson  *========================================================================*/
52023a3f90c6SAndrew Thompson 
52033a3f90c6SAndrew Thompson static void
5204ed6d949aSAndrew Thompson umidi_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
52053a3f90c6SAndrew Thompson {
5206ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usbd_xfer_softc(xfer);
52073a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub;
5208ed6d949aSAndrew Thompson 	struct usb_page_cache *pc;
52096f068a43SHans Petter Selasky 	uint8_t buf[4];
52103a3f90c6SAndrew Thompson 	uint8_t cmd_len;
52113a3f90c6SAndrew Thompson 	uint8_t cn;
52123a3f90c6SAndrew Thompson 	uint16_t pos;
5213ed6d949aSAndrew Thompson 	int actlen;
5214ed6d949aSAndrew Thompson 
5215ed6d949aSAndrew Thompson 	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
52163a3f90c6SAndrew Thompson 
52173a3f90c6SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
52183a3f90c6SAndrew Thompson 	case USB_ST_TRANSFERRED:
52193a3f90c6SAndrew Thompson 
5220ed6d949aSAndrew Thompson 		DPRINTF("actlen=%d bytes\n", actlen);
52213a3f90c6SAndrew Thompson 
52223a3f90c6SAndrew Thompson 		pos = 0;
5223ed6d949aSAndrew Thompson 		pc = usbd_xfer_get_frame(xfer, 0);
52243a3f90c6SAndrew Thompson 
5225ed6d949aSAndrew Thompson 		while (actlen >= 4) {
52263a3f90c6SAndrew Thompson 
52276f068a43SHans Petter Selasky 			/* copy out the MIDI data */
52286f068a43SHans Petter Selasky 			usbd_copy_out(pc, pos, buf, 4);
52296f068a43SHans Petter Selasky 			/* command length */
52306f068a43SHans Petter Selasky 			cmd_len = umidi_cmd_to_len[buf[0] & 0xF];
52316f068a43SHans Petter Selasky 			/* cable number */
52326f068a43SHans Petter Selasky 			cn = buf[0] >> 4;
52336f068a43SHans Petter Selasky 			/*
52346f068a43SHans Petter Selasky 			 * Lookup sub-channel. The index is range
52356f068a43SHans Petter Selasky 			 * checked below.
52366f068a43SHans Petter Selasky 			 */
52373a3f90c6SAndrew Thompson 			sub = &chan->sub[cn];
52383a3f90c6SAndrew Thompson 
52396f068a43SHans Petter Selasky 			if ((cmd_len != 0) &&
52406f068a43SHans Petter Selasky 			    (cn < chan->max_cable) &&
52416f068a43SHans Petter Selasky 			    (sub->read_open != 0)) {
52423a3f90c6SAndrew Thompson 
52436f068a43SHans Petter Selasky 				/* Send data to the application */
52446f068a43SHans Petter Selasky 				usb_fifo_put_data_linear(
52456f068a43SHans Petter Selasky 				    sub->fifo.fp[USB_FIFO_RX],
52466f068a43SHans Petter Selasky 				    buf + 1, cmd_len, 1);
52476f068a43SHans Petter Selasky 			}
5248ed6d949aSAndrew Thompson 			actlen -= 4;
52493a3f90c6SAndrew Thompson 			pos += 4;
52503a3f90c6SAndrew Thompson 		}
52513a3f90c6SAndrew Thompson 
52523a3f90c6SAndrew Thompson 	case USB_ST_SETUP:
52533a3f90c6SAndrew Thompson 		DPRINTF("start\n");
52546f068a43SHans Petter Selasky tr_setup:
5255ed6d949aSAndrew Thompson 		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
5256a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
52576f068a43SHans Petter Selasky 		break;
52583a3f90c6SAndrew Thompson 
52593a3f90c6SAndrew Thompson 	default:
5260ed6d949aSAndrew Thompson 		DPRINTF("error=%s\n", usbd_errstr(error));
52613a3f90c6SAndrew Thompson 
5262ed6d949aSAndrew Thompson 		if (error != USB_ERR_CANCELLED) {
52633a3f90c6SAndrew Thompson 			/* try to clear stall first */
52646f068a43SHans Petter Selasky 			usbd_xfer_set_stall(xfer);
52656f068a43SHans Petter Selasky 			goto tr_setup;
52663a3f90c6SAndrew Thompson 		}
52676f068a43SHans Petter Selasky 		break;
52683a3f90c6SAndrew Thompson 	}
52693a3f90c6SAndrew Thompson }
52703a3f90c6SAndrew Thompson 
52713a3f90c6SAndrew Thompson /*
52723a3f90c6SAndrew Thompson  * The following statemachine, that converts MIDI commands to
52733a3f90c6SAndrew Thompson  * USB MIDI packets, derives from Linux's usbmidi.c, which
52743a3f90c6SAndrew Thompson  * was written by "Clemens Ladisch":
52753a3f90c6SAndrew Thompson  *
52763a3f90c6SAndrew Thompson  * Returns:
52773a3f90c6SAndrew Thompson  *    0: No command
52783a3f90c6SAndrew Thompson  * Else: Command is complete
52793a3f90c6SAndrew Thompson  */
52803a3f90c6SAndrew Thompson static uint8_t
52813a3f90c6SAndrew Thompson umidi_convert_to_usb(struct umidi_sub_chan *sub, uint8_t cn, uint8_t b)
52823a3f90c6SAndrew Thompson {
52833a3f90c6SAndrew Thompson 	uint8_t p0 = (cn << 4);
52843a3f90c6SAndrew Thompson 
52853a3f90c6SAndrew Thompson 	if (b >= 0xf8) {
52863a3f90c6SAndrew Thompson 		sub->temp_0[0] = p0 | 0x0f;
52873a3f90c6SAndrew Thompson 		sub->temp_0[1] = b;
52883a3f90c6SAndrew Thompson 		sub->temp_0[2] = 0;
52893a3f90c6SAndrew Thompson 		sub->temp_0[3] = 0;
52903a3f90c6SAndrew Thompson 		sub->temp_cmd = sub->temp_0;
52913a3f90c6SAndrew Thompson 		return (1);
52923a3f90c6SAndrew Thompson 
52933a3f90c6SAndrew Thompson 	} else if (b >= 0xf0) {
52943a3f90c6SAndrew Thompson 		switch (b) {
52953a3f90c6SAndrew Thompson 		case 0xf0:		/* system exclusive begin */
52963a3f90c6SAndrew Thompson 			sub->temp_1[1] = b;
52973a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_SYSEX_1;
52983a3f90c6SAndrew Thompson 			break;
52993a3f90c6SAndrew Thompson 		case 0xf1:		/* MIDI time code */
53003a3f90c6SAndrew Thompson 		case 0xf3:		/* song select */
53013a3f90c6SAndrew Thompson 			sub->temp_1[1] = b;
53023a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_1PARAM;
53033a3f90c6SAndrew Thompson 			break;
53043a3f90c6SAndrew Thompson 		case 0xf2:		/* song position pointer */
53053a3f90c6SAndrew Thompson 			sub->temp_1[1] = b;
53063a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_2PARAM_1;
53073a3f90c6SAndrew Thompson 			break;
53083a3f90c6SAndrew Thompson 		case 0xf4:		/* unknown */
53093a3f90c6SAndrew Thompson 		case 0xf5:		/* unknown */
53103a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_UNKNOWN;
53113a3f90c6SAndrew Thompson 			break;
53123a3f90c6SAndrew Thompson 		case 0xf6:		/* tune request */
53133a3f90c6SAndrew Thompson 			sub->temp_1[0] = p0 | 0x05;
53143a3f90c6SAndrew Thompson 			sub->temp_1[1] = 0xf6;
53153a3f90c6SAndrew Thompson 			sub->temp_1[2] = 0;
53163a3f90c6SAndrew Thompson 			sub->temp_1[3] = 0;
53173a3f90c6SAndrew Thompson 			sub->temp_cmd = sub->temp_1;
53183a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_UNKNOWN;
53193a3f90c6SAndrew Thompson 			return (1);
53203a3f90c6SAndrew Thompson 
53213a3f90c6SAndrew Thompson 		case 0xf7:		/* system exclusive end */
53223a3f90c6SAndrew Thompson 			switch (sub->state) {
53233a3f90c6SAndrew Thompson 			case UMIDI_ST_SYSEX_0:
53243a3f90c6SAndrew Thompson 				sub->temp_1[0] = p0 | 0x05;
53253a3f90c6SAndrew Thompson 				sub->temp_1[1] = 0xf7;
53263a3f90c6SAndrew Thompson 				sub->temp_1[2] = 0;
53273a3f90c6SAndrew Thompson 				sub->temp_1[3] = 0;
53283a3f90c6SAndrew Thompson 				sub->temp_cmd = sub->temp_1;
53293a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_UNKNOWN;
53303a3f90c6SAndrew Thompson 				return (1);
53313a3f90c6SAndrew Thompson 			case UMIDI_ST_SYSEX_1:
53323a3f90c6SAndrew Thompson 				sub->temp_1[0] = p0 | 0x06;
53333a3f90c6SAndrew Thompson 				sub->temp_1[2] = 0xf7;
53343a3f90c6SAndrew Thompson 				sub->temp_1[3] = 0;
53353a3f90c6SAndrew Thompson 				sub->temp_cmd = sub->temp_1;
53363a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_UNKNOWN;
53373a3f90c6SAndrew Thompson 				return (1);
53383a3f90c6SAndrew Thompson 			case UMIDI_ST_SYSEX_2:
53393a3f90c6SAndrew Thompson 				sub->temp_1[0] = p0 | 0x07;
53403a3f90c6SAndrew Thompson 				sub->temp_1[3] = 0xf7;
53413a3f90c6SAndrew Thompson 				sub->temp_cmd = sub->temp_1;
53423a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_UNKNOWN;
53433a3f90c6SAndrew Thompson 				return (1);
53443a3f90c6SAndrew Thompson 			}
53453a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_UNKNOWN;
53463a3f90c6SAndrew Thompson 			break;
53473a3f90c6SAndrew Thompson 		}
53483a3f90c6SAndrew Thompson 	} else if (b >= 0x80) {
53493a3f90c6SAndrew Thompson 		sub->temp_1[1] = b;
53503a3f90c6SAndrew Thompson 		if ((b >= 0xc0) && (b <= 0xdf)) {
53513a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_1PARAM;
53523a3f90c6SAndrew Thompson 		} else {
53533a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_2PARAM_1;
53543a3f90c6SAndrew Thompson 		}
53553a3f90c6SAndrew Thompson 	} else {			/* b < 0x80 */
53563a3f90c6SAndrew Thompson 		switch (sub->state) {
53573a3f90c6SAndrew Thompson 		case UMIDI_ST_1PARAM:
53583a3f90c6SAndrew Thompson 			if (sub->temp_1[1] < 0xf0) {
53593a3f90c6SAndrew Thompson 				p0 |= sub->temp_1[1] >> 4;
53603a3f90c6SAndrew Thompson 			} else {
53613a3f90c6SAndrew Thompson 				p0 |= 0x02;
53623a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_UNKNOWN;
53633a3f90c6SAndrew Thompson 			}
53643a3f90c6SAndrew Thompson 			sub->temp_1[0] = p0;
53653a3f90c6SAndrew Thompson 			sub->temp_1[2] = b;
53663a3f90c6SAndrew Thompson 			sub->temp_1[3] = 0;
53673a3f90c6SAndrew Thompson 			sub->temp_cmd = sub->temp_1;
53683a3f90c6SAndrew Thompson 			return (1);
53693a3f90c6SAndrew Thompson 		case UMIDI_ST_2PARAM_1:
53703a3f90c6SAndrew Thompson 			sub->temp_1[2] = b;
53713a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_2PARAM_2;
53723a3f90c6SAndrew Thompson 			break;
53733a3f90c6SAndrew Thompson 		case UMIDI_ST_2PARAM_2:
53743a3f90c6SAndrew Thompson 			if (sub->temp_1[1] < 0xf0) {
53753a3f90c6SAndrew Thompson 				p0 |= sub->temp_1[1] >> 4;
53763a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_2PARAM_1;
53773a3f90c6SAndrew Thompson 			} else {
53783a3f90c6SAndrew Thompson 				p0 |= 0x03;
53793a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_UNKNOWN;
53803a3f90c6SAndrew Thompson 			}
53813a3f90c6SAndrew Thompson 			sub->temp_1[0] = p0;
53823a3f90c6SAndrew Thompson 			sub->temp_1[3] = b;
53833a3f90c6SAndrew Thompson 			sub->temp_cmd = sub->temp_1;
53843a3f90c6SAndrew Thompson 			return (1);
53853a3f90c6SAndrew Thompson 		case UMIDI_ST_SYSEX_0:
53863a3f90c6SAndrew Thompson 			sub->temp_1[1] = b;
53873a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_SYSEX_1;
53883a3f90c6SAndrew Thompson 			break;
53893a3f90c6SAndrew Thompson 		case UMIDI_ST_SYSEX_1:
53903a3f90c6SAndrew Thompson 			sub->temp_1[2] = b;
53913a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_SYSEX_2;
53923a3f90c6SAndrew Thompson 			break;
53933a3f90c6SAndrew Thompson 		case UMIDI_ST_SYSEX_2:
53943a3f90c6SAndrew Thompson 			sub->temp_1[0] = p0 | 0x04;
53953a3f90c6SAndrew Thompson 			sub->temp_1[3] = b;
53963a3f90c6SAndrew Thompson 			sub->temp_cmd = sub->temp_1;
53973a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_SYSEX_0;
53983a3f90c6SAndrew Thompson 			return (1);
53996f068a43SHans Petter Selasky 		default:
54006f068a43SHans Petter Selasky 			break;
54013a3f90c6SAndrew Thompson 		}
54023a3f90c6SAndrew Thompson 	}
54033a3f90c6SAndrew Thompson 	return (0);
54043a3f90c6SAndrew Thompson }
54053a3f90c6SAndrew Thompson 
54063a3f90c6SAndrew Thompson static void
5407ed6d949aSAndrew Thompson umidi_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
54083a3f90c6SAndrew Thompson {
5409ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usbd_xfer_softc(xfer);
54103a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub;
5411ed6d949aSAndrew Thompson 	struct usb_page_cache *pc;
54123a3f90c6SAndrew Thompson 	uint32_t actlen;
5413910f1dcfSHans Petter Selasky 	uint16_t nframes;
54143a3f90c6SAndrew Thompson 	uint8_t buf;
54153a3f90c6SAndrew Thompson 	uint8_t start_cable;
54163a3f90c6SAndrew Thompson 	uint8_t tr_any;
5417ed6d949aSAndrew Thompson 	int len;
5418ed6d949aSAndrew Thompson 
5419ed6d949aSAndrew Thompson 	usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
54203a3f90c6SAndrew Thompson 
5421910f1dcfSHans Petter Selasky 	/*
5422910f1dcfSHans Petter Selasky 	 * NOTE: Some MIDI devices only accept 4 bytes of data per
5423910f1dcfSHans Petter Selasky 	 * short terminated USB transfer.
5424910f1dcfSHans Petter Selasky 	 */
54253a3f90c6SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
54263a3f90c6SAndrew Thompson 	case USB_ST_TRANSFERRED:
5427ed6d949aSAndrew Thompson 		DPRINTF("actlen=%d bytes\n", len);
54283a3f90c6SAndrew Thompson 
54293a3f90c6SAndrew Thompson 	case USB_ST_SETUP:
54306f068a43SHans Petter Selasky tr_setup:
54313a3f90c6SAndrew Thompson 		DPRINTF("start\n");
54323a3f90c6SAndrew Thompson 
5433910f1dcfSHans Petter Selasky 		nframes = 0;	/* reset */
54343a3f90c6SAndrew Thompson 		start_cable = chan->curr_cable;
54353a3f90c6SAndrew Thompson 		tr_any = 0;
54364944c3a8SHans Petter Selasky 		pc = usbd_xfer_get_frame(xfer, 0);
54373a3f90c6SAndrew Thompson 
54383a3f90c6SAndrew Thompson 		while (1) {
54393a3f90c6SAndrew Thompson 
54403a3f90c6SAndrew Thompson 			/* round robin de-queueing */
54413a3f90c6SAndrew Thompson 
54423a3f90c6SAndrew Thompson 			sub = &chan->sub[chan->curr_cable];
54433a3f90c6SAndrew Thompson 
54443a3f90c6SAndrew Thompson 			if (sub->write_open) {
5445910f1dcfSHans Petter Selasky 				usb_fifo_get_data_linear(sub->fifo.fp[USB_FIFO_TX],
5446910f1dcfSHans Petter Selasky 				    &buf, 1, &actlen, 0);
54473a3f90c6SAndrew Thompson 			} else {
54483a3f90c6SAndrew Thompson 				actlen = 0;
54493a3f90c6SAndrew Thompson 			}
54503a3f90c6SAndrew Thompson 
54513a3f90c6SAndrew Thompson 			if (actlen) {
54523a3f90c6SAndrew Thompson 
54533a3f90c6SAndrew Thompson 				tr_any = 1;
54543a3f90c6SAndrew Thompson 
5455910f1dcfSHans Petter Selasky 				DPRINTF("byte=0x%02x from FIFO %u\n", buf,
5456910f1dcfSHans Petter Selasky 				    (unsigned int)chan->curr_cable);
54573a3f90c6SAndrew Thompson 
54583a3f90c6SAndrew Thompson 				if (umidi_convert_to_usb(sub, chan->curr_cable, buf)) {
54593a3f90c6SAndrew Thompson 
5460910f1dcfSHans Petter Selasky 					DPRINTF("sub=0x%02x 0x%02x 0x%02x 0x%02x\n",
54613a3f90c6SAndrew Thompson 					    sub->temp_cmd[0], sub->temp_cmd[1],
54623a3f90c6SAndrew Thompson 					    sub->temp_cmd[2], sub->temp_cmd[3]);
54633a3f90c6SAndrew Thompson 
54644944c3a8SHans Petter Selasky 					usbd_copy_in(pc, nframes * 4, sub->temp_cmd, 4);
5465910f1dcfSHans Petter Selasky 
5466910f1dcfSHans Petter Selasky 					nframes++;
54674944c3a8SHans Petter Selasky 
54684944c3a8SHans Petter Selasky 					if ((nframes >= UMIDI_TX_FRAMES) || (chan->single_command != 0))
54693a3f90c6SAndrew Thompson 						break;
54703a3f90c6SAndrew Thompson 				} else {
54713a3f90c6SAndrew Thompson 					continue;
54723a3f90c6SAndrew Thompson 				}
54733a3f90c6SAndrew Thompson 			}
5474910f1dcfSHans Petter Selasky 
54753a3f90c6SAndrew Thompson 			chan->curr_cable++;
5476910f1dcfSHans Petter Selasky 			if (chan->curr_cable >= chan->max_cable)
54773a3f90c6SAndrew Thompson 				chan->curr_cable = 0;
5478910f1dcfSHans Petter Selasky 
54793a3f90c6SAndrew Thompson 			if (chan->curr_cable == start_cable) {
5480910f1dcfSHans Petter Selasky 				if (tr_any == 0)
54813a3f90c6SAndrew Thompson 					break;
54823a3f90c6SAndrew Thompson 				tr_any = 0;
54833a3f90c6SAndrew Thompson 			}
54843a3f90c6SAndrew Thompson 		}
54853a3f90c6SAndrew Thompson 
54864944c3a8SHans Petter Selasky 		if (nframes != 0) {
5487910f1dcfSHans Petter Selasky 			DPRINTF("Transferring %d frames\n", (int)nframes);
54884944c3a8SHans Petter Selasky 			usbd_xfer_set_frame_len(xfer, 0, 4 * nframes);
5489a593f6b8SAndrew Thompson 			usbd_transfer_submit(xfer);
54903a3f90c6SAndrew Thompson 		}
54916f068a43SHans Petter Selasky 		break;
54923a3f90c6SAndrew Thompson 
54933a3f90c6SAndrew Thompson 	default:			/* Error */
54943a3f90c6SAndrew Thompson 
5495ed6d949aSAndrew Thompson 		DPRINTF("error=%s\n", usbd_errstr(error));
54963a3f90c6SAndrew Thompson 
5497ed6d949aSAndrew Thompson 		if (error != USB_ERR_CANCELLED) {
54983a3f90c6SAndrew Thompson 			/* try to clear stall first */
54996f068a43SHans Petter Selasky 			usbd_xfer_set_stall(xfer);
55006f068a43SHans Petter Selasky 			goto tr_setup;
55013a3f90c6SAndrew Thompson 		}
55026f068a43SHans Petter Selasky 		break;
55033a3f90c6SAndrew Thompson 	}
55043a3f90c6SAndrew Thompson }
55053a3f90c6SAndrew Thompson 
55063a3f90c6SAndrew Thompson static struct umidi_sub_chan *
5507760bc48eSAndrew Thompson umidi_sub_by_fifo(struct usb_fifo *fifo)
55083a3f90c6SAndrew Thompson {
5509ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
55103a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub;
55113a3f90c6SAndrew Thompson 	uint32_t n;
55123a3f90c6SAndrew Thompson 
55133a3f90c6SAndrew Thompson 	for (n = 0; n < UMIDI_CABLES_MAX; n++) {
55143a3f90c6SAndrew Thompson 		sub = &chan->sub[n];
55153a3f90c6SAndrew Thompson 		if ((sub->fifo.fp[USB_FIFO_RX] == fifo) ||
55163a3f90c6SAndrew Thompson 		    (sub->fifo.fp[USB_FIFO_TX] == fifo)) {
55173a3f90c6SAndrew Thompson 			return (sub);
55183a3f90c6SAndrew Thompson 		}
55193a3f90c6SAndrew Thompson 	}
55203a3f90c6SAndrew Thompson 
5521760bc48eSAndrew Thompson 	panic("%s:%d cannot find usb_fifo!\n",
55223a3f90c6SAndrew Thompson 	    __FILE__, __LINE__);
55233a3f90c6SAndrew Thompson 
55243a3f90c6SAndrew Thompson 	return (NULL);
55253a3f90c6SAndrew Thompson }
55263a3f90c6SAndrew Thompson 
55273a3f90c6SAndrew Thompson static void
5528760bc48eSAndrew Thompson umidi_start_read(struct usb_fifo *fifo)
55293a3f90c6SAndrew Thompson {
5530ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
55313a3f90c6SAndrew Thompson 
55326f068a43SHans Petter Selasky 	usbd_transfer_start(chan->xfer[UMIDI_RX_TRANSFER]);
55333a3f90c6SAndrew Thompson }
55343a3f90c6SAndrew Thompson 
55353a3f90c6SAndrew Thompson static void
5536760bc48eSAndrew Thompson umidi_stop_read(struct usb_fifo *fifo)
55373a3f90c6SAndrew Thompson {
5538ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
55393a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo);
55403a3f90c6SAndrew Thompson 
55413a3f90c6SAndrew Thompson 	DPRINTF("\n");
55423a3f90c6SAndrew Thompson 
55433a3f90c6SAndrew Thompson 	sub->read_open = 0;
55443a3f90c6SAndrew Thompson 
55453a3f90c6SAndrew Thompson 	if (--(chan->read_open_refcount) == 0) {
55463a3f90c6SAndrew Thompson 		/*
55473a3f90c6SAndrew Thompson 		 * XXX don't stop the read transfer here, hence that causes
55483a3f90c6SAndrew Thompson 		 * problems with some MIDI adapters
55493a3f90c6SAndrew Thompson 		 */
55503a3f90c6SAndrew Thompson 		DPRINTF("(stopping read transfer)\n");
55513a3f90c6SAndrew Thompson 	}
55523a3f90c6SAndrew Thompson }
55533a3f90c6SAndrew Thompson 
55543a3f90c6SAndrew Thompson static void
5555760bc48eSAndrew Thompson umidi_start_write(struct usb_fifo *fifo)
55563a3f90c6SAndrew Thompson {
5557ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
55583a3f90c6SAndrew Thompson 
55596f068a43SHans Petter Selasky 	usbd_transfer_start(chan->xfer[UMIDI_TX_TRANSFER]);
55603a3f90c6SAndrew Thompson }
55613a3f90c6SAndrew Thompson 
55623a3f90c6SAndrew Thompson static void
5563760bc48eSAndrew Thompson umidi_stop_write(struct usb_fifo *fifo)
55643a3f90c6SAndrew Thompson {
5565ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
55663a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo);
55673a3f90c6SAndrew Thompson 
55683a3f90c6SAndrew Thompson 	DPRINTF("\n");
55693a3f90c6SAndrew Thompson 
55703a3f90c6SAndrew Thompson 	sub->write_open = 0;
55713a3f90c6SAndrew Thompson 
55723a3f90c6SAndrew Thompson 	if (--(chan->write_open_refcount) == 0) {
55733a3f90c6SAndrew Thompson 		DPRINTF("(stopping write transfer)\n");
55746f068a43SHans Petter Selasky 		usbd_transfer_stop(chan->xfer[UMIDI_TX_TRANSFER]);
55753a3f90c6SAndrew Thompson 	}
55763a3f90c6SAndrew Thompson }
55773a3f90c6SAndrew Thompson 
55783a3f90c6SAndrew Thompson static int
5579760bc48eSAndrew Thompson umidi_open(struct usb_fifo *fifo, int fflags)
55803a3f90c6SAndrew Thompson {
5581ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
55823a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo);
55833a3f90c6SAndrew Thompson 
55843a3f90c6SAndrew Thompson 	if (fflags & FREAD) {
5585a593f6b8SAndrew Thompson 		if (usb_fifo_alloc_buffer(fifo, 4, (1024 / 4))) {
55863a3f90c6SAndrew Thompson 			return (ENOMEM);
55873a3f90c6SAndrew Thompson 		}
55887e6e6b67SAndrew Thompson 		mtx_lock(&chan->mtx);
55893a3f90c6SAndrew Thompson 		chan->read_open_refcount++;
55903a3f90c6SAndrew Thompson 		sub->read_open = 1;
55917e6e6b67SAndrew Thompson 		mtx_unlock(&chan->mtx);
55923a3f90c6SAndrew Thompson 	}
55933a3f90c6SAndrew Thompson 	if (fflags & FWRITE) {
5594a593f6b8SAndrew Thompson 		if (usb_fifo_alloc_buffer(fifo, 32, (1024 / 32))) {
55953a3f90c6SAndrew Thompson 			return (ENOMEM);
55963a3f90c6SAndrew Thompson 		}
55973a3f90c6SAndrew Thompson 		/* clear stall first */
55987e6e6b67SAndrew Thompson 		mtx_lock(&chan->mtx);
55993a3f90c6SAndrew Thompson 		chan->write_open_refcount++;
56003a3f90c6SAndrew Thompson 		sub->write_open = 1;
56013a3f90c6SAndrew Thompson 
56023a3f90c6SAndrew Thompson 		/* reset */
56033a3f90c6SAndrew Thompson 		sub->state = UMIDI_ST_UNKNOWN;
56047e6e6b67SAndrew Thompson 		mtx_unlock(&chan->mtx);
56053a3f90c6SAndrew Thompson 	}
56063a3f90c6SAndrew Thompson 	return (0);			/* success */
56073a3f90c6SAndrew Thompson }
56083a3f90c6SAndrew Thompson 
56093a3f90c6SAndrew Thompson static void
5610760bc48eSAndrew Thompson umidi_close(struct usb_fifo *fifo, int fflags)
56113a3f90c6SAndrew Thompson {
56123a3f90c6SAndrew Thompson 	if (fflags & FREAD) {
5613a593f6b8SAndrew Thompson 		usb_fifo_free_buffer(fifo);
56143a3f90c6SAndrew Thompson 	}
56153a3f90c6SAndrew Thompson 	if (fflags & FWRITE) {
5616a593f6b8SAndrew Thompson 		usb_fifo_free_buffer(fifo);
56173a3f90c6SAndrew Thompson 	}
56183a3f90c6SAndrew Thompson }
56193a3f90c6SAndrew Thompson 
56203a3f90c6SAndrew Thompson 
56213a3f90c6SAndrew Thompson static int
5622760bc48eSAndrew Thompson umidi_ioctl(struct usb_fifo *fifo, u_long cmd, void *data,
5623ee3e3ff5SAndrew Thompson     int fflags)
56243a3f90c6SAndrew Thompson {
56253a3f90c6SAndrew Thompson 	return (ENODEV);
56263a3f90c6SAndrew Thompson }
56273a3f90c6SAndrew Thompson 
56283a3f90c6SAndrew Thompson static void
56293a3f90c6SAndrew Thompson umidi_init(device_t dev)
56303a3f90c6SAndrew Thompson {
56313a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(dev);
56323a3f90c6SAndrew Thompson 	struct umidi_chan *chan = &sc->sc_midi_chan;
56333a3f90c6SAndrew Thompson 
56343a3f90c6SAndrew Thompson 	mtx_init(&chan->mtx, "umidi lock", NULL, MTX_DEF | MTX_RECURSE);
56353a3f90c6SAndrew Thompson }
56363a3f90c6SAndrew Thompson 
5637760bc48eSAndrew Thompson static struct usb_fifo_methods umidi_fifo_methods = {
56383a3f90c6SAndrew Thompson 	.f_start_read = &umidi_start_read,
56393a3f90c6SAndrew Thompson 	.f_start_write = &umidi_start_write,
56403a3f90c6SAndrew Thompson 	.f_stop_read = &umidi_stop_read,
56413a3f90c6SAndrew Thompson 	.f_stop_write = &umidi_stop_write,
56423a3f90c6SAndrew Thompson 	.f_open = &umidi_open,
56433a3f90c6SAndrew Thompson 	.f_close = &umidi_close,
56443a3f90c6SAndrew Thompson 	.f_ioctl = &umidi_ioctl,
56453a3f90c6SAndrew Thompson 	.basename[0] = "umidi",
56463a3f90c6SAndrew Thompson };
56473a3f90c6SAndrew Thompson 
564825b74dabSHans Petter Selasky static int
56493a3f90c6SAndrew Thompson umidi_probe(device_t dev)
56503a3f90c6SAndrew Thompson {
56513a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(dev);
5652760bc48eSAndrew Thompson 	struct usb_attach_arg *uaa = device_get_ivars(dev);
56533a3f90c6SAndrew Thompson 	struct umidi_chan *chan = &sc->sc_midi_chan;
56543a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub;
56553a3f90c6SAndrew Thompson 	int unit = device_get_unit(dev);
56563a3f90c6SAndrew Thompson 	int error;
56573a3f90c6SAndrew Thompson 	uint32_t n;
56583a3f90c6SAndrew Thompson 
56594944c3a8SHans Petter Selasky 	if (usb_test_quirk(uaa, UQ_SINGLE_CMD_MIDI))
56604944c3a8SHans Petter Selasky 		chan->single_command = 1;
56614944c3a8SHans Petter Selasky 
5662a593f6b8SAndrew Thompson 	if (usbd_set_alt_interface_index(sc->sc_udev, chan->iface_index,
56633a3f90c6SAndrew Thompson 	    chan->iface_alt_index)) {
56643a3f90c6SAndrew Thompson 		DPRINTF("setting of alternate index failed!\n");
56653a3f90c6SAndrew Thompson 		goto detach;
56663a3f90c6SAndrew Thompson 	}
56676f068a43SHans Petter Selasky 	usbd_set_parent_iface(sc->sc_udev, chan->iface_index,
56686f068a43SHans Petter Selasky 	    sc->sc_mixer_iface_index);
56693a3f90c6SAndrew Thompson 
5670a593f6b8SAndrew Thompson 	error = usbd_transfer_setup(uaa->device, &chan->iface_index,
56713a3f90c6SAndrew Thompson 	    chan->xfer, umidi_config, UMIDI_N_TRANSFER,
56723a3f90c6SAndrew Thompson 	    chan, &chan->mtx);
56733a3f90c6SAndrew Thompson 	if (error) {
5674a593f6b8SAndrew Thompson 		DPRINTF("error=%s\n", usbd_errstr(error));
56753a3f90c6SAndrew Thompson 		goto detach;
56763a3f90c6SAndrew Thompson 	}
5677e91fe3a9SHans Petter Selasky 
5678e91fe3a9SHans Petter Selasky 	/*
5679e91fe3a9SHans Petter Selasky 	 * Some USB MIDI device makers couldn't resist using
5680e91fe3a9SHans Petter Selasky 	 * wMaxPacketSize = 4 for RX and TX BULK endpoints, although
5681e91fe3a9SHans Petter Selasky 	 * that size is an unsupported value for FULL speed BULK
5682e91fe3a9SHans Petter Selasky 	 * endpoints. The same applies to some HIGH speed MIDI devices
5683e91fe3a9SHans Petter Selasky 	 * which are using a wMaxPacketSize different from 512 bytes.
5684e91fe3a9SHans Petter Selasky 	 *
5685e91fe3a9SHans Petter Selasky 	 * Refer to section 5.8.3 in USB 2.0 PDF: Cite: "All Host
5686e91fe3a9SHans Petter Selasky 	 * Controllers are required to have support for 8-, 16-, 32-,
5687e91fe3a9SHans Petter Selasky 	 * and 64-byte maximum packet sizes for full-speed bulk
5688e91fe3a9SHans Petter Selasky 	 * endpoints and 512 bytes for high-speed bulk endpoints."
5689e91fe3a9SHans Petter Selasky 	 */
5690e91fe3a9SHans Petter Selasky 	if (usbd_xfer_maxp_was_clamped(chan->xfer[UMIDI_TX_TRANSFER]))
5691e91fe3a9SHans Petter Selasky 		chan->single_command = 1;
5692e91fe3a9SHans Petter Selasky 
5693e91fe3a9SHans Petter Selasky 	if (chan->single_command != 0)
5694e91fe3a9SHans Petter Selasky 		device_printf(dev, "Single command MIDI quirk enabled\n");
5695e91fe3a9SHans Petter Selasky 
56963a3f90c6SAndrew Thompson 	if ((chan->max_cable > UMIDI_CABLES_MAX) ||
56973a3f90c6SAndrew Thompson 	    (chan->max_cable == 0)) {
56983a3f90c6SAndrew Thompson 		chan->max_cable = UMIDI_CABLES_MAX;
56993a3f90c6SAndrew Thompson 	}
57003a3f90c6SAndrew Thompson 
57013a3f90c6SAndrew Thompson 	for (n = 0; n < chan->max_cable; n++) {
57023a3f90c6SAndrew Thompson 
57033a3f90c6SAndrew Thompson 		sub = &chan->sub[n];
57043a3f90c6SAndrew Thompson 
5705a593f6b8SAndrew Thompson 		error = usb_fifo_attach(sc->sc_udev, chan, &chan->mtx,
57063a3f90c6SAndrew Thompson 		    &umidi_fifo_methods, &sub->fifo, unit, n,
5707ee3e3ff5SAndrew Thompson 		    chan->iface_index,
5708ee3e3ff5SAndrew Thompson 		    UID_ROOT, GID_OPERATOR, 0644);
57093a3f90c6SAndrew Thompson 		if (error) {
57103a3f90c6SAndrew Thompson 			goto detach;
57113a3f90c6SAndrew Thompson 		}
57123a3f90c6SAndrew Thompson 	}
57133a3f90c6SAndrew Thompson 
57143a3f90c6SAndrew Thompson 	mtx_lock(&chan->mtx);
57153a3f90c6SAndrew Thompson 
57163a3f90c6SAndrew Thompson 	/*
57176f068a43SHans Petter Selasky 	 * NOTE: At least one device will not work properly unless the
57186f068a43SHans Petter Selasky 	 * BULK IN pipe is open all the time. This might have to do
57196f068a43SHans Petter Selasky 	 * about that the internal queues of the device overflow if we
57206f068a43SHans Petter Selasky 	 * don't read them regularly.
57213a3f90c6SAndrew Thompson 	 */
57226f068a43SHans Petter Selasky 	usbd_transfer_start(chan->xfer[UMIDI_RX_TRANSFER]);
57233a3f90c6SAndrew Thompson 
57243a3f90c6SAndrew Thompson 	mtx_unlock(&chan->mtx);
57253a3f90c6SAndrew Thompson 
57263a3f90c6SAndrew Thompson 	return (0);			/* success */
57273a3f90c6SAndrew Thompson 
57283a3f90c6SAndrew Thompson detach:
57293a3f90c6SAndrew Thompson 	return (ENXIO);			/* failure */
57303a3f90c6SAndrew Thompson }
57313a3f90c6SAndrew Thompson 
573225b74dabSHans Petter Selasky static int
57333a3f90c6SAndrew Thompson umidi_detach(device_t dev)
57343a3f90c6SAndrew Thompson {
57353a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(dev);
57363a3f90c6SAndrew Thompson 	struct umidi_chan *chan = &sc->sc_midi_chan;
57373a3f90c6SAndrew Thompson 	uint32_t n;
57383a3f90c6SAndrew Thompson 
57393a3f90c6SAndrew Thompson 	for (n = 0; n < UMIDI_CABLES_MAX; n++) {
5740a593f6b8SAndrew Thompson 		usb_fifo_detach(&chan->sub[n].fifo);
57413a3f90c6SAndrew Thompson 	}
57423a3f90c6SAndrew Thompson 
57433a3f90c6SAndrew Thompson 	mtx_lock(&chan->mtx);
57443a3f90c6SAndrew Thompson 
57456f068a43SHans Petter Selasky 	usbd_transfer_stop(chan->xfer[UMIDI_RX_TRANSFER]);
57463a3f90c6SAndrew Thompson 
57473a3f90c6SAndrew Thompson 	mtx_unlock(&chan->mtx);
57483a3f90c6SAndrew Thompson 
5749a593f6b8SAndrew Thompson 	usbd_transfer_unsetup(chan->xfer, UMIDI_N_TRANSFER);
57503a3f90c6SAndrew Thompson 
57513a3f90c6SAndrew Thompson 	mtx_destroy(&chan->mtx);
57523a3f90c6SAndrew Thompson 
57533a3f90c6SAndrew Thompson 	return (0);
57543a3f90c6SAndrew Thompson }
57553a3f90c6SAndrew Thompson 
575676b71212SHans Petter Selasky static void
575776b71212SHans Petter Selasky uaudio_hid_rx_callback(struct usb_xfer *xfer, usb_error_t error)
575876b71212SHans Petter Selasky {
575976b71212SHans Petter Selasky 	struct uaudio_softc *sc = usbd_xfer_softc(xfer);
576076b71212SHans Petter Selasky 	const uint8_t *buffer = usbd_xfer_get_frame_buffer(xfer, 0);
576176b71212SHans Petter Selasky 	struct snd_mixer *m;
576276b71212SHans Petter Selasky 	uint8_t id;
576376b71212SHans Petter Selasky 	int actlen;
576476b71212SHans Petter Selasky 
576576b71212SHans Petter Selasky 	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
576676b71212SHans Petter Selasky 
576776b71212SHans Petter Selasky 	switch (USB_GET_STATE(xfer)) {
576876b71212SHans Petter Selasky 	case USB_ST_TRANSFERRED:
576976b71212SHans Petter Selasky 		DPRINTF("actlen=%d\n", actlen);
577076b71212SHans Petter Selasky 
577176b71212SHans Petter Selasky 		if (actlen != 0 &&
577276b71212SHans Petter Selasky 		    (sc->sc_hid.flags & UAUDIO_HID_HAS_ID)) {
577376b71212SHans Petter Selasky 			id = *buffer;
577476b71212SHans Petter Selasky 			buffer++;
577576b71212SHans Petter Selasky 			actlen--;
577676b71212SHans Petter Selasky 		} else {
577776b71212SHans Petter Selasky 			id = 0;
577876b71212SHans Petter Selasky 		}
577976b71212SHans Petter Selasky 
578076b71212SHans Petter Selasky 		m = sc->sc_mixer_dev;
578176b71212SHans Petter Selasky 
57822ba0f361SHans Petter Selasky 		if ((sc->sc_hid.flags & UAUDIO_HID_HAS_MUTE) &&
57832ba0f361SHans Petter Selasky 		    (sc->sc_hid.mute_id == id) &&
57842ba0f361SHans Petter Selasky 		    hid_get_data(buffer, actlen,
57852ba0f361SHans Petter Selasky 		    &sc->sc_hid.mute_loc)) {
57862ba0f361SHans Petter Selasky 
57872ba0f361SHans Petter Selasky 			DPRINTF("Mute toggle\n");
57882ba0f361SHans Petter Selasky 
57892ba0f361SHans Petter Selasky 			mixer_hwvol_mute_locked(m);
57902ba0f361SHans Petter Selasky 		}
57912ba0f361SHans Petter Selasky 
579276b71212SHans Petter Selasky 		if ((sc->sc_hid.flags & UAUDIO_HID_HAS_VOLUME_UP) &&
579376b71212SHans Petter Selasky 		    (sc->sc_hid.volume_up_id == id) &&
579476b71212SHans Petter Selasky 		    hid_get_data(buffer, actlen,
579576b71212SHans Petter Selasky 		    &sc->sc_hid.volume_up_loc)) {
579676b71212SHans Petter Selasky 
579776b71212SHans Petter Selasky 			DPRINTF("Volume Up\n");
579876b71212SHans Petter Selasky 
57992ba0f361SHans Petter Selasky 			mixer_hwvol_step_locked(m, 1, 1);
580076b71212SHans Petter Selasky 		}
580176b71212SHans Petter Selasky 
580276b71212SHans Petter Selasky 		if ((sc->sc_hid.flags & UAUDIO_HID_HAS_VOLUME_DOWN) &&
580376b71212SHans Petter Selasky 		    (sc->sc_hid.volume_down_id == id) &&
580476b71212SHans Petter Selasky 		    hid_get_data(buffer, actlen,
580576b71212SHans Petter Selasky 		    &sc->sc_hid.volume_down_loc)) {
580676b71212SHans Petter Selasky 
580776b71212SHans Petter Selasky 			DPRINTF("Volume Down\n");
580876b71212SHans Petter Selasky 
58092ba0f361SHans Petter Selasky 			mixer_hwvol_step_locked(m, -1, -1);
581076b71212SHans Petter Selasky 		}
581176b71212SHans Petter Selasky 
581276b71212SHans Petter Selasky 	case USB_ST_SETUP:
581376b71212SHans Petter Selasky tr_setup:
581476b71212SHans Petter Selasky 		/* check if we can put more data into the FIFO */
581576b71212SHans Petter Selasky 		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
581676b71212SHans Petter Selasky 		usbd_transfer_submit(xfer);
581776b71212SHans Petter Selasky 		break;
581876b71212SHans Petter Selasky 
581976b71212SHans Petter Selasky 	default:			/* Error */
5820e5359a3bSHans Petter Selasky 
5821e5359a3bSHans Petter Selasky 		DPRINTF("error=%s\n", usbd_errstr(error));
5822e5359a3bSHans Petter Selasky 
582376b71212SHans Petter Selasky 		if (error != USB_ERR_CANCELLED) {
5824e5359a3bSHans Petter Selasky 			/* try to clear stall first */
582576b71212SHans Petter Selasky 			usbd_xfer_set_stall(xfer);
582676b71212SHans Petter Selasky 			goto tr_setup;
582776b71212SHans Petter Selasky 		}
582876b71212SHans Petter Selasky 		break;
582976b71212SHans Petter Selasky 	}
583076b71212SHans Petter Selasky }
583176b71212SHans Petter Selasky 
583276b71212SHans Petter Selasky static int
583376b71212SHans Petter Selasky uaudio_hid_probe(struct uaudio_softc *sc,
583476b71212SHans Petter Selasky     struct usb_attach_arg *uaa)
583576b71212SHans Petter Selasky {
583676b71212SHans Petter Selasky 	void *d_ptr;
583776b71212SHans Petter Selasky 	uint32_t flags;
583876b71212SHans Petter Selasky 	uint16_t d_len;
583976b71212SHans Petter Selasky 	uint8_t id;
584076b71212SHans Petter Selasky 	int error;
584176b71212SHans Petter Selasky 
584276b71212SHans Petter Selasky 	if (!(sc->sc_hid.flags & UAUDIO_HID_VALID))
584376b71212SHans Petter Selasky 		return (-1);
584476b71212SHans Petter Selasky 
584576b71212SHans Petter Selasky 	if (sc->sc_mixer_lock == NULL)
584676b71212SHans Petter Selasky 		return (-1);
584776b71212SHans Petter Selasky 
584876b71212SHans Petter Selasky 	/* Get HID descriptor */
584976b71212SHans Petter Selasky 	error = usbd_req_get_hid_desc(uaa->device, NULL, &d_ptr,
585076b71212SHans Petter Selasky 	    &d_len, M_TEMP, sc->sc_hid.iface_index);
585176b71212SHans Petter Selasky 
585276b71212SHans Petter Selasky 	if (error) {
585376b71212SHans Petter Selasky 		DPRINTF("error reading report description\n");
585476b71212SHans Petter Selasky 		return (-1);
585576b71212SHans Petter Selasky 	}
585676b71212SHans Petter Selasky 
585776b71212SHans Petter Selasky 	/* check if there is an ID byte */
585876b71212SHans Petter Selasky 	hid_report_size(d_ptr, d_len, hid_input, &id);
585976b71212SHans Petter Selasky 
586076b71212SHans Petter Selasky 	if (id != 0)
586176b71212SHans Petter Selasky 		sc->sc_hid.flags |= UAUDIO_HID_HAS_ID;
586276b71212SHans Petter Selasky 
586376b71212SHans Petter Selasky 	if (hid_locate(d_ptr, d_len,
586476b71212SHans Petter Selasky 	    HID_USAGE2(HUP_CONSUMER, 0xE9 /* Volume Increment */),
586576b71212SHans Petter Selasky 	    hid_input, 0, &sc->sc_hid.volume_up_loc, &flags,
586676b71212SHans Petter Selasky 	    &sc->sc_hid.volume_up_id)) {
586776b71212SHans Petter Selasky 		if (flags & HIO_VARIABLE)
586876b71212SHans Petter Selasky 			sc->sc_hid.flags |= UAUDIO_HID_HAS_VOLUME_UP;
586976b71212SHans Petter Selasky 		DPRINTFN(1, "Found Volume Up key\n");
587076b71212SHans Petter Selasky 	}
587176b71212SHans Petter Selasky 
587276b71212SHans Petter Selasky 	if (hid_locate(d_ptr, d_len,
587376b71212SHans Petter Selasky 	    HID_USAGE2(HUP_CONSUMER, 0xEA /* Volume Decrement */),
587476b71212SHans Petter Selasky 	    hid_input, 0, &sc->sc_hid.volume_down_loc, &flags,
587576b71212SHans Petter Selasky 	    &sc->sc_hid.volume_down_id)) {
587676b71212SHans Petter Selasky 		if (flags & HIO_VARIABLE)
587776b71212SHans Petter Selasky 			sc->sc_hid.flags |= UAUDIO_HID_HAS_VOLUME_DOWN;
587876b71212SHans Petter Selasky 		DPRINTFN(1, "Found Volume Down key\n");
587976b71212SHans Petter Selasky 	}
588076b71212SHans Petter Selasky 
58812ba0f361SHans Petter Selasky 	if (hid_locate(d_ptr, d_len,
58822ba0f361SHans Petter Selasky 	    HID_USAGE2(HUP_CONSUMER, 0xE2 /* Mute */),
58832ba0f361SHans Petter Selasky 	    hid_input, 0, &sc->sc_hid.mute_loc, &flags,
58842ba0f361SHans Petter Selasky 	    &sc->sc_hid.mute_id)) {
58852ba0f361SHans Petter Selasky 		if (flags & HIO_VARIABLE)
58862ba0f361SHans Petter Selasky 			sc->sc_hid.flags |= UAUDIO_HID_HAS_MUTE;
58872ba0f361SHans Petter Selasky 		DPRINTFN(1, "Found Mute key\n");
58882ba0f361SHans Petter Selasky 	}
58892ba0f361SHans Petter Selasky 
589076b71212SHans Petter Selasky 	free(d_ptr, M_TEMP);
589176b71212SHans Petter Selasky 
589276b71212SHans Petter Selasky 	if (!(sc->sc_hid.flags & (UAUDIO_HID_HAS_VOLUME_UP |
58932ba0f361SHans Petter Selasky 	    UAUDIO_HID_HAS_VOLUME_DOWN |
58942ba0f361SHans Petter Selasky 	    UAUDIO_HID_HAS_MUTE))) {
589576b71212SHans Petter Selasky 		DPRINTFN(1, "Did not find any volume related keys\n");
589676b71212SHans Petter Selasky 		return (-1);
589776b71212SHans Petter Selasky 	}
589876b71212SHans Petter Selasky 
589976b71212SHans Petter Selasky 	/* prevent the uhid driver from attaching */
590076b71212SHans Petter Selasky 	usbd_set_parent_iface(uaa->device, sc->sc_hid.iface_index,
590176b71212SHans Petter Selasky 	    sc->sc_mixer_iface_index);
590276b71212SHans Petter Selasky 
590376b71212SHans Petter Selasky 	/* allocate USB transfers */
590476b71212SHans Petter Selasky 	error = usbd_transfer_setup(uaa->device, &sc->sc_hid.iface_index,
590576b71212SHans Petter Selasky 	    sc->sc_hid.xfer, uaudio_hid_config, UAUDIO_HID_N_TRANSFER,
590676b71212SHans Petter Selasky 	    sc, sc->sc_mixer_lock);
590776b71212SHans Petter Selasky 	if (error) {
590876b71212SHans Petter Selasky 		DPRINTF("error=%s\n", usbd_errstr(error));
590976b71212SHans Petter Selasky 		return (-1);
591076b71212SHans Petter Selasky 	}
591176b71212SHans Petter Selasky 	return (0);
591276b71212SHans Petter Selasky }
591376b71212SHans Petter Selasky 
591476b71212SHans Petter Selasky static void
591576b71212SHans Petter Selasky uaudio_hid_detach(struct uaudio_softc *sc)
591676b71212SHans Petter Selasky {
591776b71212SHans Petter Selasky 	usbd_transfer_unsetup(sc->sc_hid.xfer, UAUDIO_HID_N_TRANSFER);
591876b71212SHans Petter Selasky }
591976b71212SHans Petter Selasky 
5920af26e3dfSHans Petter Selasky DRIVER_MODULE_ORDERED(uaudio, uhub, uaudio_driver, uaudio_devclass, NULL, 0, SI_ORDER_ANY);
59213a3f90c6SAndrew Thompson MODULE_DEPEND(uaudio, usb, 1, 1, 1);
59223a3f90c6SAndrew Thompson MODULE_DEPEND(uaudio, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
59233a3f90c6SAndrew Thompson MODULE_VERSION(uaudio, 1);
5924