xref: /freebsd/sys/dev/sound/usb/uaudio.c (revision f809f280)
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 
104ece4b0bdSHans Petter Selasky SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, debug, CTLFLAG_RWTUN,
1053a3f90c6SAndrew Thompson     &uaudio_debug, 0, "uaudio debug level");
106af3b2549SHans Petter Selasky SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_rate, CTLFLAG_RWTUN,
1073a3f90c6SAndrew Thompson     &uaudio_default_rate, 0, "uaudio default sample rate");
108af3b2549SHans Petter Selasky SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_bits, CTLFLAG_RWTUN,
1093a3f90c6SAndrew Thompson     &uaudio_default_bits, 0, "uaudio default sample bits");
110af3b2549SHans Petter Selasky SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_channels, CTLFLAG_RWTUN,
1113a3f90c6SAndrew Thompson     &uaudio_default_channels, 0, "uaudio default sample channels");
1123a3f90c6SAndrew Thompson #endif
1133a3f90c6SAndrew Thompson 
114a931ce67SHans Petter Selasky #define	UAUDIO_IRQS	(8000 / UAUDIO_NFRAMES)	/* interrupts per second */
115b029f6bbSAndrew Thompson #define	UAUDIO_NFRAMES		64	/* must be factor of 8 due HS-USB */
1163a3f90c6SAndrew Thompson #define	UAUDIO_NCHANBUFS	2	/* number of outstanding request */
117e2524b2eSHans Petter Selasky #define	UAUDIO_RECURSE_LIMIT	255	/* rounds */
1189dd12733SHans Petter Selasky #define	UAUDIO_CHANNELS_MAX	MIN(64, AFMT_CHANNEL_MAX)
1199dd12733SHans Petter Selasky #define	UAUDIO_MATRIX_MAX	8	/* channels */
1203a3f90c6SAndrew Thompson 
1213a3f90c6SAndrew Thompson #define	MAKE_WORD(h,l) (((h) << 8) | (l))
1223a3f90c6SAndrew Thompson #define	BIT_TEST(bm,bno) (((bm)[(bno) / 8] >> (7 - ((bno) % 8))) & 1)
123c737a632SAndrew Thompson #define	UAUDIO_MAX_CHAN(x) (x)
124f7e62ad0SHans Petter Selasky #define	MIX(sc) ((sc)->sc_mixer_node)
1253a3f90c6SAndrew Thompson 
126e2524b2eSHans Petter Selasky union uaudio_asid {
127e2524b2eSHans Petter Selasky 	const struct usb_audio_streaming_interface_descriptor *v1;
128e2524b2eSHans Petter Selasky 	const struct usb_audio20_streaming_interface_descriptor *v2;
129e2524b2eSHans Petter Selasky };
130e2524b2eSHans Petter Selasky 
131e2524b2eSHans Petter Selasky union uaudio_asf1d {
132e2524b2eSHans Petter Selasky 	const struct usb_audio_streaming_type1_descriptor *v1;
133e2524b2eSHans Petter Selasky 	const struct usb_audio20_streaming_type1_descriptor *v2;
134e2524b2eSHans Petter Selasky };
135e2524b2eSHans Petter Selasky 
136e2524b2eSHans Petter Selasky union uaudio_sed {
137e2524b2eSHans Petter Selasky 	const struct usb_audio_streaming_endpoint_descriptor *v1;
138e2524b2eSHans Petter Selasky 	const struct usb_audio20_streaming_endpoint_descriptor *v2;
139e2524b2eSHans Petter Selasky };
140e2524b2eSHans Petter Selasky 
1413a3f90c6SAndrew Thompson struct uaudio_mixer_node {
142902514f6SHans Petter Selasky 	const char *name;
143902514f6SHans Petter Selasky 
1443a3f90c6SAndrew Thompson 	int32_t	minval;
1453a3f90c6SAndrew Thompson 	int32_t	maxval;
146902514f6SHans Petter Selasky #define	MIX_MAX_CHAN 16
1473a3f90c6SAndrew Thompson 	int32_t	wValue[MIX_MAX_CHAN];	/* using nchan */
1483a3f90c6SAndrew Thompson 	uint32_t mul;
1493a3f90c6SAndrew Thompson 	uint32_t ctl;
1503a3f90c6SAndrew Thompson 
151902514f6SHans Petter Selasky 	int wData[MIX_MAX_CHAN];	/* using nchan */
1523a3f90c6SAndrew Thompson 	uint16_t wIndex;
1533a3f90c6SAndrew Thompson 
1543a3f90c6SAndrew Thompson 	uint8_t	update[(MIX_MAX_CHAN + 7) / 8];
1553a3f90c6SAndrew Thompson 	uint8_t	nchan;
1563a3f90c6SAndrew Thompson 	uint8_t	type;
1573a3f90c6SAndrew Thompson #define	MIX_ON_OFF	1
1583a3f90c6SAndrew Thompson #define	MIX_SIGNED_16	2
1593a3f90c6SAndrew Thompson #define	MIX_UNSIGNED_16	3
1603a3f90c6SAndrew Thompson #define	MIX_SIGNED_8	4
1613a3f90c6SAndrew Thompson #define	MIX_SELECTOR	5
1623a3f90c6SAndrew Thompson #define	MIX_UNKNOWN     6
1633a3f90c6SAndrew Thompson #define	MIX_SIZE(n) ((((n) == MIX_SIGNED_16) || \
1643a3f90c6SAndrew Thompson 		      ((n) == MIX_UNSIGNED_16)) ? 2 : 1)
1653a3f90c6SAndrew Thompson #define	MIX_UNSIGNED(n) ((n) == MIX_UNSIGNED_16)
1663a3f90c6SAndrew Thompson 
1673a3f90c6SAndrew Thompson #define	MAX_SELECTOR_INPUT_PIN 256
1683a3f90c6SAndrew Thompson 	uint8_t	slctrtype[MAX_SELECTOR_INPUT_PIN];
1693a3f90c6SAndrew Thompson 	uint8_t	class;
170ff4d5953SHans Petter Selasky 	uint8_t val_default;
1713a3f90c6SAndrew Thompson 
172902514f6SHans Petter Selasky 	uint8_t desc[64];
173902514f6SHans Petter Selasky 
1743a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *next;
1753a3f90c6SAndrew Thompson };
1763a3f90c6SAndrew Thompson 
177455a367fSHans Petter Selasky struct uaudio_configure_msg {
178455a367fSHans Petter Selasky 	struct usb_proc_msg hdr;
179455a367fSHans Petter Selasky 	struct uaudio_softc *sc;
180455a367fSHans Petter Selasky };
1813a3f90c6SAndrew Thompson 
1822c2752d3SHans Petter Selasky #define	CHAN_MAX_ALT 24
183455a367fSHans Petter Selasky 
184455a367fSHans Petter Selasky struct uaudio_chan_alt {
185e2524b2eSHans Petter Selasky 	union uaudio_asf1d p_asf1d;
186e2524b2eSHans Petter Selasky 	union uaudio_sed p_sed;
1874c21be9bSRebecca Cran 	const usb_endpoint_descriptor_audio_t *p_ed1;
1883a3f90c6SAndrew Thompson 	const struct uaudio_format *p_fmt;
189455a367fSHans Petter Selasky 	const struct usb_config *usb_cfg;
190455a367fSHans Petter Selasky 	uint32_t sample_rate;	/* in Hz */
191455a367fSHans Petter Selasky 	uint16_t sample_size;
192455a367fSHans Petter Selasky 	uint8_t	iface_index;
193455a367fSHans Petter Selasky 	uint8_t	iface_alt_index;
194455a367fSHans Petter Selasky 	uint8_t channels;
195455a367fSHans Petter Selasky };
196455a367fSHans Petter Selasky 
197455a367fSHans Petter Selasky struct uaudio_chan {
198455a367fSHans Petter Selasky 	struct pcmchan_caps pcm_cap;	/* capabilities */
199455a367fSHans Petter Selasky 	struct uaudio_chan_alt usb_alt[CHAN_MAX_ALT];
200455a367fSHans Petter Selasky 	struct snd_dbuf *pcm_buf;
201455a367fSHans Petter Selasky 	struct mtx *pcm_mtx;		/* lock protecting this structure */
202455a367fSHans Petter Selasky 	struct uaudio_softc *priv_sc;
203455a367fSHans Petter Selasky 	struct pcm_channel *pcm_ch;
204455a367fSHans Petter Selasky 	struct usb_xfer *xfer[UAUDIO_NCHANBUFS + 1];
2053a3f90c6SAndrew Thompson 
2063a3f90c6SAndrew Thompson 	uint8_t *buf;			/* pointer to buffer */
2073a3f90c6SAndrew Thompson 	uint8_t *start;			/* upper layer buffer start */
2083a3f90c6SAndrew Thompson 	uint8_t *end;			/* upper layer buffer end */
2093a3f90c6SAndrew Thompson 	uint8_t *cur;			/* current position in upper layer
2103a3f90c6SAndrew Thompson 					 * buffer */
2113a3f90c6SAndrew Thompson 
212b029f6bbSAndrew Thompson 	uint32_t intr_frames;		/* in units */
213afbfddd9SAndrew Thompson 	uint32_t frames_per_second;
214afbfddd9SAndrew Thompson 	uint32_t sample_rem;
215afbfddd9SAndrew Thompson 	uint32_t sample_curr;
216455a367fSHans Petter Selasky 	uint32_t max_buf;
21785bad582SHans Petter Selasky 	int32_t jitter_rem;
21885bad582SHans Petter Selasky 	int32_t jitter_curr;
21985bad582SHans Petter Selasky 
22085bad582SHans Petter Selasky 	int feedback_rate;
221afbfddd9SAndrew Thompson 
2223a3f90c6SAndrew Thompson 	uint32_t pcm_format[2];
2233a3f90c6SAndrew Thompson 
224afbfddd9SAndrew Thompson 	uint16_t bytes_per_frame[2];
225afbfddd9SAndrew Thompson 
22685bad582SHans Petter Selasky 	uint32_t intr_counter;
22785bad582SHans Petter Selasky 	uint32_t running;
22885bad582SHans Petter Selasky 	uint32_t num_alt;
22985bad582SHans Petter Selasky 	uint32_t cur_alt;
23085bad582SHans Petter Selasky 	uint32_t set_alt;
23185bad582SHans Petter Selasky 	uint32_t operation;
232455a367fSHans Petter Selasky #define	CHAN_OP_NONE 0
233455a367fSHans Petter Selasky #define	CHAN_OP_START 1
234455a367fSHans Petter Selasky #define	CHAN_OP_STOP 2
235455a367fSHans Petter Selasky #define	CHAN_OP_DRAIN 3
2363a3f90c6SAndrew Thompson };
2373a3f90c6SAndrew Thompson 
2388bf51ab5SHans Petter Selasky #define	UMIDI_EMB_JACK_MAX   16		/* units */
239e0b17a62SHans Petter Selasky #define	UMIDI_TX_FRAMES	   256		/* units */
240910f1dcfSHans Petter Selasky #define	UMIDI_TX_BUFFER    (UMIDI_TX_FRAMES * 4)	/* bytes */
2413a3f90c6SAndrew Thompson 
2426f068a43SHans Petter Selasky enum {
2436f068a43SHans Petter Selasky 	UMIDI_TX_TRANSFER,
2446f068a43SHans Petter Selasky 	UMIDI_RX_TRANSFER,
2456f068a43SHans Petter Selasky 	UMIDI_N_TRANSFER,
2466f068a43SHans Petter Selasky };
2476f068a43SHans Petter Selasky 
2483a3f90c6SAndrew Thompson struct umidi_sub_chan {
249760bc48eSAndrew Thompson 	struct usb_fifo_sc fifo;
2503a3f90c6SAndrew Thompson 	uint8_t *temp_cmd;
2513a3f90c6SAndrew Thompson 	uint8_t	temp_0[4];
2523a3f90c6SAndrew Thompson 	uint8_t	temp_1[4];
2533a3f90c6SAndrew Thompson 	uint8_t	state;
2543a3f90c6SAndrew Thompson #define	UMIDI_ST_UNKNOWN   0		/* scan for command */
2553a3f90c6SAndrew Thompson #define	UMIDI_ST_1PARAM    1
2563a3f90c6SAndrew Thompson #define	UMIDI_ST_2PARAM_1  2
2573a3f90c6SAndrew Thompson #define	UMIDI_ST_2PARAM_2  3
2583a3f90c6SAndrew Thompson #define	UMIDI_ST_SYSEX_0   4
2593a3f90c6SAndrew Thompson #define	UMIDI_ST_SYSEX_1   5
2603a3f90c6SAndrew Thompson #define	UMIDI_ST_SYSEX_2   6
2613a3f90c6SAndrew Thompson 
2623a3f90c6SAndrew Thompson 	uint8_t	read_open:1;
2633a3f90c6SAndrew Thompson 	uint8_t	write_open:1;
2643a3f90c6SAndrew Thompson 	uint8_t	unused:6;
2653a3f90c6SAndrew Thompson };
2663a3f90c6SAndrew Thompson 
2673a3f90c6SAndrew Thompson struct umidi_chan {
2683a3f90c6SAndrew Thompson 
2698bf51ab5SHans Petter Selasky 	struct umidi_sub_chan sub[UMIDI_EMB_JACK_MAX];
2703a3f90c6SAndrew Thompson 	struct mtx mtx;
2713a3f90c6SAndrew Thompson 
272760bc48eSAndrew Thompson 	struct usb_xfer *xfer[UMIDI_N_TRANSFER];
2733a3f90c6SAndrew Thompson 
2743a3f90c6SAndrew Thompson 	uint8_t	iface_index;
2753a3f90c6SAndrew Thompson 	uint8_t	iface_alt_index;
2763a3f90c6SAndrew Thompson 
2773a3f90c6SAndrew Thompson 	uint8_t	read_open_refcount;
2783a3f90c6SAndrew Thompson 	uint8_t	write_open_refcount;
2793a3f90c6SAndrew Thompson 
2803a3f90c6SAndrew Thompson 	uint8_t	curr_cable;
2818bf51ab5SHans Petter Selasky 	uint8_t	max_emb_jack;
2823a3f90c6SAndrew Thompson 	uint8_t	valid;
2834944c3a8SHans Petter Selasky 	uint8_t single_command;
2843a3f90c6SAndrew Thompson };
2853a3f90c6SAndrew Thompson 
286e2524b2eSHans Petter Selasky struct uaudio_search_result {
287e2524b2eSHans Petter Selasky 	uint8_t	bit_input[(256 + 7) / 8];
288e2524b2eSHans Petter Selasky 	uint8_t	bit_output[(256 + 7) / 8];
289e2524b2eSHans Petter Selasky 	uint8_t	recurse_level;
290e2524b2eSHans Petter Selasky 	uint8_t	id_max;
291e2524b2eSHans Petter Selasky 	uint8_t is_input;
292e2524b2eSHans Petter Selasky };
293e2524b2eSHans Petter Selasky 
29476b71212SHans Petter Selasky enum {
29576b71212SHans Petter Selasky 	UAUDIO_HID_RX_TRANSFER,
29676b71212SHans Petter Selasky 	UAUDIO_HID_N_TRANSFER,
29776b71212SHans Petter Selasky };
29876b71212SHans Petter Selasky 
29976b71212SHans Petter Selasky struct uaudio_hid {
30076b71212SHans Petter Selasky 	struct usb_xfer *xfer[UAUDIO_HID_N_TRANSFER];
30176b71212SHans Petter Selasky 	struct hid_location volume_up_loc;
30276b71212SHans Petter Selasky 	struct hid_location volume_down_loc;
3032ba0f361SHans Petter Selasky 	struct hid_location mute_loc;
30476b71212SHans Petter Selasky 	uint32_t flags;
30576b71212SHans Petter Selasky #define	UAUDIO_HID_VALID		0x0001
30676b71212SHans Petter Selasky #define	UAUDIO_HID_HAS_ID		0x0002
30776b71212SHans Petter Selasky #define	UAUDIO_HID_HAS_VOLUME_UP	0x0004
30876b71212SHans Petter Selasky #define	UAUDIO_HID_HAS_VOLUME_DOWN	0x0008
3092ba0f361SHans Petter Selasky #define	UAUDIO_HID_HAS_MUTE		0x0010
31076b71212SHans Petter Selasky 	uint8_t iface_index;
31176b71212SHans Petter Selasky 	uint8_t volume_up_id;
31276b71212SHans Petter Selasky 	uint8_t volume_down_id;
3132ba0f361SHans Petter Selasky 	uint8_t mute_id;
31476b71212SHans Petter Selasky };
31576b71212SHans Petter Selasky 
3163a3f90c6SAndrew Thompson struct uaudio_softc {
3173a3f90c6SAndrew Thompson 	struct sbuf sc_sndstat;
3183a3f90c6SAndrew Thompson 	struct sndcard_func sc_sndcard_func;
3193a3f90c6SAndrew Thompson 	struct uaudio_chan sc_rec_chan;
3203a3f90c6SAndrew Thompson 	struct uaudio_chan sc_play_chan;
3213a3f90c6SAndrew Thompson 	struct umidi_chan sc_midi_chan;
32276b71212SHans Petter Selasky 	struct uaudio_hid sc_hid;
323e2524b2eSHans Petter Selasky 	struct uaudio_search_result sc_mixer_clocks;
324f7e62ad0SHans Petter Selasky 	struct uaudio_mixer_node sc_mixer_node;
325455a367fSHans Petter Selasky 	struct uaudio_configure_msg sc_config_msg[2];
3263a3f90c6SAndrew Thompson 
327902514f6SHans Petter Selasky 	struct mtx *sc_mixer_lock;
32876b71212SHans Petter Selasky 	struct snd_mixer *sc_mixer_dev;
329760bc48eSAndrew Thompson 	struct usb_device *sc_udev;
330760bc48eSAndrew Thompson 	struct usb_xfer *sc_mixer_xfer[1];
3313a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *sc_mixer_root;
3323a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *sc_mixer_curr;
3333a3f90c6SAndrew Thompson 
3343a3f90c6SAndrew Thompson 	uint32_t sc_mix_info;
3353a3f90c6SAndrew Thompson 	uint32_t sc_recsrc_info;
3363a3f90c6SAndrew Thompson 
3373a3f90c6SAndrew Thompson 	uint16_t sc_audio_rev;
3383a3f90c6SAndrew Thompson 	uint16_t sc_mixer_count;
3393a3f90c6SAndrew Thompson 
3403a3f90c6SAndrew Thompson 	uint8_t	sc_sndstat_valid;
3413a3f90c6SAndrew Thompson 	uint8_t	sc_mixer_iface_index;
3423a3f90c6SAndrew Thompson 	uint8_t	sc_mixer_iface_no;
3433a3f90c6SAndrew Thompson 	uint8_t	sc_mixer_chan;
3443a3f90c6SAndrew Thompson 	uint8_t	sc_pcm_registered:1;
3453a3f90c6SAndrew Thompson 	uint8_t	sc_mixer_init:1;
3463a3f90c6SAndrew Thompson 	uint8_t	sc_uq_audio_swap_lr:1;
3473a3f90c6SAndrew Thompson 	uint8_t	sc_uq_au_inp_async:1;
3483a3f90c6SAndrew Thompson 	uint8_t	sc_uq_au_no_xu:1;
3493a3f90c6SAndrew Thompson 	uint8_t	sc_uq_bad_adc:1;
35025b74dabSHans Petter Selasky 	uint8_t	sc_uq_au_vendor_class:1;
3519dd12733SHans Petter Selasky 	uint8_t	sc_pcm_bitperfect:1;
3523a3f90c6SAndrew Thompson };
3533a3f90c6SAndrew Thompson 
3543a3f90c6SAndrew Thompson struct uaudio_terminal_node {
3553a3f90c6SAndrew Thompson 	union {
356760bc48eSAndrew Thompson 		const struct usb_descriptor *desc;
357e2524b2eSHans Petter Selasky 		const struct usb_audio_input_terminal *it_v1;
358e2524b2eSHans Petter Selasky 		const struct usb_audio_output_terminal *ot_v1;
359e2524b2eSHans Petter Selasky 		const struct usb_audio_mixer_unit_0 *mu_v1;
360e2524b2eSHans Petter Selasky 		const struct usb_audio_selector_unit *su_v1;
361e2524b2eSHans Petter Selasky 		const struct usb_audio_feature_unit *fu_v1;
362e2524b2eSHans Petter Selasky 		const struct usb_audio_processing_unit_0 *pu_v1;
363e2524b2eSHans Petter Selasky 		const struct usb_audio_extension_unit_0 *eu_v1;
364e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_source_unit *csrc_v2;
365e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_selector_unit_0 *csel_v2;
366e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_multiplier_unit *cmul_v2;
367e2524b2eSHans Petter Selasky 		const struct usb_audio20_input_terminal *it_v2;
368e2524b2eSHans Petter Selasky 		const struct usb_audio20_output_terminal *ot_v2;
369e2524b2eSHans Petter Selasky 		const struct usb_audio20_mixer_unit_0 *mu_v2;
370e2524b2eSHans Petter Selasky 		const struct usb_audio20_selector_unit *su_v2;
371e2524b2eSHans Petter Selasky 		const struct usb_audio20_feature_unit *fu_v2;
372e2524b2eSHans Petter Selasky 		const struct usb_audio20_sample_rate_unit *ru_v2;
373e2524b2eSHans Petter Selasky 		const struct usb_audio20_processing_unit_0 *pu_v2;
374e2524b2eSHans Petter Selasky 		const struct usb_audio20_extension_unit_0 *eu_v2;
375e2524b2eSHans Petter Selasky 		const struct usb_audio20_effect_unit *ef_v2;
3763a3f90c6SAndrew Thompson 	}	u;
3773a3f90c6SAndrew Thompson 	struct uaudio_search_result usr;
3783a3f90c6SAndrew Thompson 	struct uaudio_terminal_node *root;
3793a3f90c6SAndrew Thompson };
3803a3f90c6SAndrew Thompson 
3813a3f90c6SAndrew Thompson struct uaudio_format {
3823a3f90c6SAndrew Thompson 	uint16_t wFormat;
3833a3f90c6SAndrew Thompson 	uint8_t	bPrecision;
3843a3f90c6SAndrew Thompson 	uint32_t freebsd_fmt;
3853a3f90c6SAndrew Thompson 	const char *description;
3863a3f90c6SAndrew Thompson };
3873a3f90c6SAndrew Thompson 
388e2524b2eSHans Petter Selasky static const struct uaudio_format uaudio10_formats[] = {
3893a3f90c6SAndrew Thompson 
3903a3f90c6SAndrew Thompson 	{UA_FMT_PCM8, 8, AFMT_U8, "8-bit U-LE PCM"},
3913a3f90c6SAndrew Thompson 	{UA_FMT_PCM8, 16, AFMT_U16_LE, "16-bit U-LE PCM"},
3923a3f90c6SAndrew Thompson 	{UA_FMT_PCM8, 24, AFMT_U24_LE, "24-bit U-LE PCM"},
3933a3f90c6SAndrew Thompson 	{UA_FMT_PCM8, 32, AFMT_U32_LE, "32-bit U-LE PCM"},
3943a3f90c6SAndrew Thompson 
3953a3f90c6SAndrew Thompson 	{UA_FMT_PCM, 8, AFMT_S8, "8-bit S-LE PCM"},
3963a3f90c6SAndrew Thompson 	{UA_FMT_PCM, 16, AFMT_S16_LE, "16-bit S-LE PCM"},
3973a3f90c6SAndrew Thompson 	{UA_FMT_PCM, 24, AFMT_S24_LE, "24-bit S-LE PCM"},
3983a3f90c6SAndrew Thompson 	{UA_FMT_PCM, 32, AFMT_S32_LE, "32-bit S-LE PCM"},
3993a3f90c6SAndrew Thompson 
4003a3f90c6SAndrew Thompson 	{UA_FMT_ALAW, 8, AFMT_A_LAW, "8-bit A-Law"},
4013a3f90c6SAndrew Thompson 	{UA_FMT_MULAW, 8, AFMT_MU_LAW, "8-bit mu-Law"},
4023a3f90c6SAndrew Thompson 
4033a3f90c6SAndrew Thompson 	{0, 0, 0, NULL}
4043a3f90c6SAndrew Thompson };
4053a3f90c6SAndrew Thompson 
406e2524b2eSHans Petter Selasky static const struct uaudio_format uaudio20_formats[] = {
407e2524b2eSHans Petter Selasky 
408e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM, 8, AFMT_S8, "8-bit S-LE PCM"},
409e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM, 16, AFMT_S16_LE, "16-bit S-LE PCM"},
410e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM, 24, AFMT_S24_LE, "24-bit S-LE PCM"},
411e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM, 32, AFMT_S32_LE, "32-bit S-LE PCM"},
412e2524b2eSHans Petter Selasky 
413e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM8, 8, AFMT_U8, "8-bit U-LE PCM"},
414e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM8, 16, AFMT_U16_LE, "16-bit U-LE PCM"},
415e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM8, 24, AFMT_U24_LE, "24-bit U-LE PCM"},
416e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM8, 32, AFMT_U32_LE, "32-bit U-LE PCM"},
417e2524b2eSHans Petter Selasky 
418e2524b2eSHans Petter Selasky 	{UA20_FMT_ALAW, 8, AFMT_A_LAW, "8-bit A-Law"},
419e2524b2eSHans Petter Selasky 	{UA20_FMT_MULAW, 8, AFMT_MU_LAW, "8-bit mu-Law"},
420e2524b2eSHans Petter Selasky 
421e2524b2eSHans Petter Selasky 	{0, 0, 0, NULL}
422e2524b2eSHans Petter Selasky };
423e2524b2eSHans Petter Selasky 
4243a3f90c6SAndrew Thompson #define	UAC_OUTPUT	0
4253a3f90c6SAndrew Thompson #define	UAC_INPUT	1
4263a3f90c6SAndrew Thompson #define	UAC_EQUAL	2
4273a3f90c6SAndrew Thompson #define	UAC_RECORD	3
4283a3f90c6SAndrew Thompson #define	UAC_NCLASSES	4
4293a3f90c6SAndrew Thompson 
430b850ecc1SAndrew Thompson #ifdef USB_DEBUG
4313a3f90c6SAndrew Thompson static const char *uac_names[] = {
4323a3f90c6SAndrew Thompson 	"outputs", "inputs", "equalization", "record"
4333a3f90c6SAndrew Thompson };
4343a3f90c6SAndrew Thompson 
4353a3f90c6SAndrew Thompson #endif
4363a3f90c6SAndrew Thompson 
4373a3f90c6SAndrew Thompson /* prototypes */
4383a3f90c6SAndrew Thompson 
4393a3f90c6SAndrew Thompson static device_probe_t uaudio_probe;
4403a3f90c6SAndrew Thompson static device_attach_t uaudio_attach;
4413a3f90c6SAndrew Thompson static device_detach_t uaudio_detach;
4423a3f90c6SAndrew Thompson 
443e0a69b51SAndrew Thompson static usb_callback_t uaudio_chan_play_callback;
444b4380da7SHans Petter Selasky static usb_callback_t uaudio_chan_play_sync_callback;
445e0a69b51SAndrew Thompson static usb_callback_t uaudio_chan_record_callback;
446b4380da7SHans Petter Selasky static usb_callback_t uaudio_chan_record_sync_callback;
447e0a69b51SAndrew Thompson static usb_callback_t uaudio_mixer_write_cfg_callback;
448e0a69b51SAndrew Thompson static usb_callback_t umidi_bulk_read_callback;
449e0a69b51SAndrew Thompson static usb_callback_t umidi_bulk_write_callback;
45076b71212SHans Petter Selasky static usb_callback_t uaudio_hid_rx_callback;
4513a3f90c6SAndrew Thompson 
452455a367fSHans Petter Selasky static usb_proc_callback_t uaudio_configure_msg;
453455a367fSHans Petter Selasky 
454902514f6SHans Petter Selasky /* ==== USB mixer ==== */
455902514f6SHans Petter Selasky 
456902514f6SHans Petter Selasky static int uaudio_mixer_sysctl_handler(SYSCTL_HANDLER_ARGS);
457902514f6SHans Petter Selasky static void uaudio_mixer_ctl_free(struct uaudio_softc *);
458902514f6SHans Petter Selasky static void uaudio_mixer_register_sysctl(struct uaudio_softc *, device_t);
459902514f6SHans Petter Selasky static void uaudio_mixer_reload_all(struct uaudio_softc *);
460ff4d5953SHans Petter Selasky static void uaudio_mixer_controls_create_ftu(struct uaudio_softc *);
461902514f6SHans Petter Selasky 
462e2524b2eSHans Petter Selasky /* ==== USB audio v1.0 ==== */
463e2524b2eSHans Petter Selasky 
4643a3f90c6SAndrew Thompson static void	uaudio_mixer_add_mixer(struct uaudio_softc *,
4653a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
4663a3f90c6SAndrew Thompson static void	uaudio_mixer_add_selector(struct uaudio_softc *,
4673a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
4683a3f90c6SAndrew Thompson static uint32_t	uaudio_mixer_feature_get_bmaControls(
4694c21be9bSRebecca Cran 		    const struct usb_audio_feature_unit *, uint8_t);
4703a3f90c6SAndrew Thompson static void	uaudio_mixer_add_feature(struct uaudio_softc *,
4713a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
4723a3f90c6SAndrew Thompson static void	uaudio_mixer_add_processing_updown(struct uaudio_softc *,
4733a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
4743a3f90c6SAndrew Thompson static void	uaudio_mixer_add_processing(struct uaudio_softc *,
4753a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
4763a3f90c6SAndrew Thompson static void	uaudio_mixer_add_extension(struct uaudio_softc *,
4773a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
4784c21be9bSRebecca Cran static struct	usb_audio_cluster uaudio_mixer_get_cluster(uint8_t,
4793a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *);
4803a3f90c6SAndrew Thompson static uint16_t	uaudio_mixer_determine_class(const struct uaudio_terminal_node *,
4813a3f90c6SAndrew Thompson 		    struct uaudio_mixer_node *);
4823a3f90c6SAndrew Thompson static uint16_t	uaudio_mixer_feature_name(const struct uaudio_terminal_node *,
4833a3f90c6SAndrew Thompson 		    struct uaudio_mixer_node *);
484e2524b2eSHans Petter Selasky static void	uaudio_mixer_find_inputs_sub(struct uaudio_terminal_node *,
485e2524b2eSHans Petter Selasky 		    const uint8_t *, uint8_t, struct uaudio_search_result *);
486e2524b2eSHans Petter Selasky static const void *uaudio_mixer_verify_desc(const void *, uint32_t);
487e2524b2eSHans Petter Selasky static usb_error_t uaudio_set_speed(struct usb_device *, uint8_t, uint32_t);
488e2524b2eSHans Petter Selasky static int	uaudio_mixer_get(struct usb_device *, uint16_t, uint8_t,
489e2524b2eSHans Petter Selasky 		    struct uaudio_mixer_node *);
490e2524b2eSHans Petter Selasky 
491e2524b2eSHans Petter Selasky /* ==== USB audio v2.0 ==== */
492e2524b2eSHans Petter Selasky 
493e2524b2eSHans Petter Selasky static void	uaudio20_mixer_add_mixer(struct uaudio_softc *,
494e2524b2eSHans Petter Selasky 		    const struct uaudio_terminal_node *, int);
495e2524b2eSHans Petter Selasky static void	uaudio20_mixer_add_selector(struct uaudio_softc *,
496e2524b2eSHans Petter Selasky 		    const struct uaudio_terminal_node *, int);
497e2524b2eSHans Petter Selasky static void	uaudio20_mixer_add_feature(struct uaudio_softc *,
498e2524b2eSHans Petter Selasky 		    const struct uaudio_terminal_node *, int);
499e2524b2eSHans Petter Selasky static struct	usb_audio20_cluster uaudio20_mixer_get_cluster(uint8_t,
500e2524b2eSHans Petter Selasky 		    const struct uaudio_terminal_node *);
501e2524b2eSHans Petter Selasky static uint16_t	uaudio20_mixer_determine_class(const struct uaudio_terminal_node *,
502e2524b2eSHans Petter Selasky 		    struct uaudio_mixer_node *);
503e2524b2eSHans Petter Selasky static uint16_t	uaudio20_mixer_feature_name(const struct uaudio_terminal_node *,
504e2524b2eSHans Petter Selasky 		    struct uaudio_mixer_node *);
505e2524b2eSHans Petter Selasky static void	uaudio20_mixer_find_inputs_sub(struct uaudio_terminal_node *,
506e2524b2eSHans Petter Selasky 		    const uint8_t *, uint8_t, struct uaudio_search_result *);
507e2524b2eSHans Petter Selasky static const void *uaudio20_mixer_verify_desc(const void *, uint32_t);
508e2524b2eSHans Petter Selasky static usb_error_t uaudio20_set_speed(struct usb_device *, uint8_t,
509e2524b2eSHans Petter Selasky 		    uint8_t, uint32_t);
510e2524b2eSHans Petter Selasky 
511e2524b2eSHans Petter Selasky /* USB audio v1.0 and v2.0 */
512e2524b2eSHans Petter Selasky 
513e2524b2eSHans Petter Selasky static void	uaudio_chan_fill_info_sub(struct uaudio_softc *,
514e2524b2eSHans Petter Selasky 		    struct usb_device *, uint32_t, uint8_t, uint8_t);
515e2524b2eSHans Petter Selasky static void	uaudio_chan_fill_info(struct uaudio_softc *,
516e2524b2eSHans Petter Selasky 		    struct usb_device *);
517e2524b2eSHans Petter Selasky static void	uaudio_mixer_add_ctl_sub(struct uaudio_softc *,
518e2524b2eSHans Petter Selasky 		    struct uaudio_mixer_node *);
519e2524b2eSHans Petter Selasky static void	uaudio_mixer_add_ctl(struct uaudio_softc *,
520e2524b2eSHans Petter Selasky 		    struct uaudio_mixer_node *);
521e2524b2eSHans Petter Selasky static void	uaudio_mixer_fill_info(struct uaudio_softc *,
522e2524b2eSHans Petter Selasky 		    struct usb_device *, void *);
523e2524b2eSHans Petter Selasky static void	uaudio_mixer_ctl_set(struct uaudio_softc *,
524e2524b2eSHans Petter Selasky 		    struct uaudio_mixer_node *, uint8_t, int32_t val);
525e2524b2eSHans Petter Selasky static int	uaudio_mixer_signext(uint8_t, int);
526e2524b2eSHans Petter Selasky static int	uaudio_mixer_bsd2value(struct uaudio_mixer_node *, int32_t val);
527e2524b2eSHans Petter Selasky static void	uaudio_mixer_init(struct uaudio_softc *);
5283a3f90c6SAndrew Thompson static const struct uaudio_terminal_node *uaudio_mixer_get_input(
5293a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, uint8_t);
5303a3f90c6SAndrew Thompson static const struct uaudio_terminal_node *uaudio_mixer_get_output(
5313a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, uint8_t);
5323a3f90c6SAndrew Thompson static void	uaudio_mixer_find_outputs_sub(struct uaudio_terminal_node *,
5333a3f90c6SAndrew Thompson 		    uint8_t, uint8_t, struct uaudio_search_result *);
5343a3f90c6SAndrew Thompson static uint8_t	umidi_convert_to_usb(struct umidi_sub_chan *, uint8_t, uint8_t);
535760bc48eSAndrew Thompson static struct	umidi_sub_chan *umidi_sub_by_fifo(struct usb_fifo *);
536760bc48eSAndrew Thompson static void	umidi_start_read(struct usb_fifo *);
537760bc48eSAndrew Thompson static void	umidi_stop_read(struct usb_fifo *);
538760bc48eSAndrew Thompson static void	umidi_start_write(struct usb_fifo *);
539760bc48eSAndrew Thompson static void	umidi_stop_write(struct usb_fifo *);
540760bc48eSAndrew Thompson static int	umidi_open(struct usb_fifo *, int);
541760bc48eSAndrew Thompson static int	umidi_ioctl(struct usb_fifo *, u_long cmd, void *, int);
542760bc48eSAndrew Thompson static void	umidi_close(struct usb_fifo *, int);
5433a3f90c6SAndrew Thompson static void	umidi_init(device_t dev);
54425b74dabSHans Petter Selasky static int	umidi_probe(device_t dev);
54525b74dabSHans Petter Selasky static int	umidi_detach(device_t dev);
54676b71212SHans Petter Selasky static int	uaudio_hid_probe(struct uaudio_softc *sc,
54776b71212SHans Petter Selasky 		    struct usb_attach_arg *uaa);
54876b71212SHans Petter Selasky static void	uaudio_hid_detach(struct uaudio_softc *sc);
5493a3f90c6SAndrew Thompson 
550b850ecc1SAndrew Thompson #ifdef USB_DEBUG
5513a3f90c6SAndrew Thompson static void	uaudio_chan_dump_ep_desc(
5524c21be9bSRebecca Cran 		    const usb_endpoint_descriptor_audio_t *);
5533a3f90c6SAndrew Thompson #endif
5543a3f90c6SAndrew Thompson 
555760bc48eSAndrew Thompson static const struct usb_config
556b4380da7SHans Petter Selasky 	uaudio_cfg_record[UAUDIO_NCHANBUFS + 1] = {
5573a3f90c6SAndrew Thompson 	[0] = {
5583a3f90c6SAndrew Thompson 		.type = UE_ISOCHRONOUS,
5593a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
5603a3f90c6SAndrew Thompson 		.direction = UE_DIR_IN,
5614eae601eSAndrew Thompson 		.bufsize = 0,	/* use "wMaxPacketSize * frames" */
562b029f6bbSAndrew Thompson 		.frames = UAUDIO_NFRAMES,
5634eae601eSAndrew Thompson 		.flags = {.short_xfer_ok = 1,},
5644eae601eSAndrew Thompson 		.callback = &uaudio_chan_record_callback,
5653a3f90c6SAndrew Thompson 	},
5663a3f90c6SAndrew Thompson 
5673a3f90c6SAndrew Thompson 	[1] = {
5683a3f90c6SAndrew Thompson 		.type = UE_ISOCHRONOUS,
5693a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
5703a3f90c6SAndrew Thompson 		.direction = UE_DIR_IN,
5714eae601eSAndrew Thompson 		.bufsize = 0,	/* use "wMaxPacketSize * frames" */
572b029f6bbSAndrew Thompson 		.frames = UAUDIO_NFRAMES,
5734eae601eSAndrew Thompson 		.flags = {.short_xfer_ok = 1,},
5744eae601eSAndrew Thompson 		.callback = &uaudio_chan_record_callback,
5753a3f90c6SAndrew Thompson 	},
576b4380da7SHans Petter Selasky 
577b4380da7SHans Petter Selasky 	[2] = {
578b4380da7SHans Petter Selasky 		.type = UE_ISOCHRONOUS,
579b4380da7SHans Petter Selasky 		.endpoint = UE_ADDR_ANY,
580b4380da7SHans Petter Selasky 		.direction = UE_DIR_OUT,
581b4380da7SHans Petter Selasky 		.bufsize = 0,	/* use "wMaxPacketSize * frames" */
582b4380da7SHans Petter Selasky 		.frames = 1,
583b4380da7SHans Petter Selasky 		.flags = {.no_pipe_ok = 1,.short_xfer_ok = 1,},
584b4380da7SHans Petter Selasky 		.callback = &uaudio_chan_record_sync_callback,
585b4380da7SHans Petter Selasky 	},
5863a3f90c6SAndrew Thompson };
5873a3f90c6SAndrew Thompson 
588760bc48eSAndrew Thompson static const struct usb_config
589b4380da7SHans Petter Selasky 	uaudio_cfg_play[UAUDIO_NCHANBUFS + 1] = {
5903a3f90c6SAndrew Thompson 	[0] = {
5913a3f90c6SAndrew Thompson 		.type = UE_ISOCHRONOUS,
5923a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
5933a3f90c6SAndrew Thompson 		.direction = UE_DIR_OUT,
5944eae601eSAndrew Thompson 		.bufsize = 0,	/* use "wMaxPacketSize * frames" */
595b029f6bbSAndrew Thompson 		.frames = UAUDIO_NFRAMES,
5964eae601eSAndrew Thompson 		.flags = {.short_xfer_ok = 1,},
5974eae601eSAndrew Thompson 		.callback = &uaudio_chan_play_callback,
5983a3f90c6SAndrew Thompson 	},
5993a3f90c6SAndrew Thompson 
6003a3f90c6SAndrew Thompson 	[1] = {
6013a3f90c6SAndrew Thompson 		.type = UE_ISOCHRONOUS,
6023a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
6033a3f90c6SAndrew Thompson 		.direction = UE_DIR_OUT,
6044eae601eSAndrew Thompson 		.bufsize = 0,	/* use "wMaxPacketSize * frames" */
605b029f6bbSAndrew Thompson 		.frames = UAUDIO_NFRAMES,
6064eae601eSAndrew Thompson 		.flags = {.short_xfer_ok = 1,},
6074eae601eSAndrew Thompson 		.callback = &uaudio_chan_play_callback,
6083a3f90c6SAndrew Thompson 	},
609b4380da7SHans Petter Selasky 
610b4380da7SHans Petter Selasky 	[2] = {
611b4380da7SHans Petter Selasky 		.type = UE_ISOCHRONOUS,
612b4380da7SHans Petter Selasky 		.endpoint = UE_ADDR_ANY,
613b4380da7SHans Petter Selasky 		.direction = UE_DIR_IN,
614b4380da7SHans Petter Selasky 		.bufsize = 0,	/* use "wMaxPacketSize * frames" */
615b4380da7SHans Petter Selasky 		.frames = 1,
616b4380da7SHans Petter Selasky 		.flags = {.no_pipe_ok = 1,.short_xfer_ok = 1,},
617b4380da7SHans Petter Selasky 		.callback = &uaudio_chan_play_sync_callback,
618b4380da7SHans Petter Selasky 	},
6193a3f90c6SAndrew Thompson };
6203a3f90c6SAndrew Thompson 
621760bc48eSAndrew Thompson static const struct usb_config
6223a3f90c6SAndrew Thompson 	uaudio_mixer_config[1] = {
6233a3f90c6SAndrew Thompson 	[0] = {
6243a3f90c6SAndrew Thompson 		.type = UE_CONTROL,
6253a3f90c6SAndrew Thompson 		.endpoint = 0x00,	/* Control pipe */
6263a3f90c6SAndrew Thompson 		.direction = UE_DIR_ANY,
627760bc48eSAndrew Thompson 		.bufsize = (sizeof(struct usb_device_request) + 4),
6284eae601eSAndrew Thompson 		.callback = &uaudio_mixer_write_cfg_callback,
6294eae601eSAndrew Thompson 		.timeout = 1000,	/* 1 second */
6303a3f90c6SAndrew Thompson 	},
6313a3f90c6SAndrew Thompson };
6323a3f90c6SAndrew Thompson 
6333a3f90c6SAndrew Thompson static const
6343a3f90c6SAndrew Thompson uint8_t	umidi_cmd_to_len[16] = {
6353a3f90c6SAndrew Thompson 	[0x0] = 0,			/* reserved */
6363a3f90c6SAndrew Thompson 	[0x1] = 0,			/* reserved */
6373a3f90c6SAndrew Thompson 	[0x2] = 2,			/* bytes */
6383a3f90c6SAndrew Thompson 	[0x3] = 3,			/* bytes */
6393a3f90c6SAndrew Thompson 	[0x4] = 3,			/* bytes */
6403a3f90c6SAndrew Thompson 	[0x5] = 1,			/* bytes */
6413a3f90c6SAndrew Thompson 	[0x6] = 2,			/* bytes */
6423a3f90c6SAndrew Thompson 	[0x7] = 3,			/* bytes */
6433a3f90c6SAndrew Thompson 	[0x8] = 3,			/* bytes */
6443a3f90c6SAndrew Thompson 	[0x9] = 3,			/* bytes */
6453a3f90c6SAndrew Thompson 	[0xA] = 3,			/* bytes */
6463a3f90c6SAndrew Thompson 	[0xB] = 3,			/* bytes */
6473a3f90c6SAndrew Thompson 	[0xC] = 2,			/* bytes */
6483a3f90c6SAndrew Thompson 	[0xD] = 2,			/* bytes */
6493a3f90c6SAndrew Thompson 	[0xE] = 3,			/* bytes */
6503a3f90c6SAndrew Thompson 	[0xF] = 1,			/* bytes */
6513a3f90c6SAndrew Thompson };
6523a3f90c6SAndrew Thompson 
653760bc48eSAndrew Thompson static const struct usb_config
6543a3f90c6SAndrew Thompson 	umidi_config[UMIDI_N_TRANSFER] = {
6556f068a43SHans Petter Selasky 	[UMIDI_TX_TRANSFER] = {
6563a3f90c6SAndrew Thompson 		.type = UE_BULK,
6573a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
6583a3f90c6SAndrew Thompson 		.direction = UE_DIR_OUT,
659910f1dcfSHans Petter Selasky 		.bufsize = UMIDI_TX_BUFFER,
6604eae601eSAndrew Thompson 		.callback = &umidi_bulk_write_callback,
6613a3f90c6SAndrew Thompson 	},
6623a3f90c6SAndrew Thompson 
6636f068a43SHans Petter Selasky 	[UMIDI_RX_TRANSFER] = {
6643a3f90c6SAndrew Thompson 		.type = UE_BULK,
6653a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
6663a3f90c6SAndrew Thompson 		.direction = UE_DIR_IN,
6673851442cSAndrew Thompson 		.bufsize = 4,	/* bytes */
668910f1dcfSHans Petter Selasky 		.flags = {.short_xfer_ok = 1,.proxy_buffer = 1,},
6694eae601eSAndrew Thompson 		.callback = &umidi_bulk_read_callback,
6703a3f90c6SAndrew Thompson 	},
6713a3f90c6SAndrew Thompson };
6723a3f90c6SAndrew Thompson 
67376b71212SHans Petter Selasky static const struct usb_config
67476b71212SHans Petter Selasky 	uaudio_hid_config[UAUDIO_HID_N_TRANSFER] = {
67576b71212SHans Petter Selasky 	[UAUDIO_HID_RX_TRANSFER] = {
67676b71212SHans Petter Selasky 		.type = UE_INTERRUPT,
67776b71212SHans Petter Selasky 		.endpoint = UE_ADDR_ANY,
67876b71212SHans Petter Selasky 		.direction = UE_DIR_IN,
67976b71212SHans Petter Selasky 		.bufsize = 0,	/* use wMaxPacketSize */
68076b71212SHans Petter Selasky 		.flags = {.short_xfer_ok = 1,},
68176b71212SHans Petter Selasky 		.callback = &uaudio_hid_rx_callback,
68276b71212SHans Petter Selasky 	},
68376b71212SHans Petter Selasky };
68476b71212SHans Petter Selasky 
6853a3f90c6SAndrew Thompson static devclass_t uaudio_devclass;
6863a3f90c6SAndrew Thompson 
6873a3f90c6SAndrew Thompson static device_method_t uaudio_methods[] = {
6883a3f90c6SAndrew Thompson 	DEVMETHOD(device_probe, uaudio_probe),
6893a3f90c6SAndrew Thompson 	DEVMETHOD(device_attach, uaudio_attach),
6903a3f90c6SAndrew Thompson 	DEVMETHOD(device_detach, uaudio_detach),
6913a3f90c6SAndrew Thompson 	DEVMETHOD(device_suspend, bus_generic_suspend),
6923a3f90c6SAndrew Thompson 	DEVMETHOD(device_resume, bus_generic_resume),
6933a3f90c6SAndrew Thompson 	DEVMETHOD(device_shutdown, bus_generic_shutdown),
6944b7ec270SMarius Strobl 
6954b7ec270SMarius Strobl 	DEVMETHOD_END
6963a3f90c6SAndrew Thompson };
6973a3f90c6SAndrew Thompson 
6983a3f90c6SAndrew Thompson static driver_t uaudio_driver = {
6993a3f90c6SAndrew Thompson 	.name = "uaudio",
7003a3f90c6SAndrew Thompson 	.methods = uaudio_methods,
7013a3f90c6SAndrew Thompson 	.size = sizeof(struct uaudio_softc),
7023a3f90c6SAndrew Thompson };
7033a3f90c6SAndrew Thompson 
70446f0b27aSHans Petter Selasky /* The following table is derived from Linux's quirks-table.h */
70546f0b27aSHans Petter Selasky static const STRUCT_USB_HOST_ID uaudio_vendor_midi[] = {
70646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1000, 0) }, /* UX256 */
70746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1001, 0) }, /* MU1000 */
70846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1002, 0) }, /* MU2000 */
70946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1003, 0) }, /* MU500 */
71046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1004, 3) }, /* UW500 */
71146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1005, 0) }, /* MOTIF6 */
71246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1006, 0) }, /* MOTIF7 */
71346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1007, 0) }, /* MOTIF8 */
71446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1008, 0) }, /* UX96 */
71546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1009, 0) }, /* UX16 */
71646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x100a, 3) }, /* EOS BX */
71746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x100c, 0) }, /* UC-MX */
71846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x100d, 0) }, /* UC-KX */
71946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x100e, 0) }, /* S08 */
72046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x100f, 0) }, /* CLP-150 */
72146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1010, 0) }, /* CLP-170 */
72246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1011, 0) }, /* P-250 */
72346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1012, 0) }, /* TYROS */
72446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1013, 0) }, /* PF-500 */
72546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1014, 0) }, /* S90 */
72646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1015, 0) }, /* MOTIF-R */
72746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1016, 0) }, /* MDP-5 */
72846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1017, 0) }, /* CVP-204 */
72946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1018, 0) }, /* CVP-206 */
73046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1019, 0) }, /* CVP-208 */
73146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x101a, 0) }, /* CVP-210 */
73246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x101b, 0) }, /* PSR-1100 */
73346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x101c, 0) }, /* PSR-2100 */
73446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x101d, 0) }, /* CLP-175 */
73546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x101e, 0) }, /* PSR-K1 */
73646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x101f, 0) }, /* EZ-J24 */
73746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1020, 0) }, /* EZ-250i */
73846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1021, 0) }, /* MOTIF ES 6 */
73946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1022, 0) }, /* MOTIF ES 7 */
74046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1023, 0) }, /* MOTIF ES 8 */
74146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1024, 0) }, /* CVP-301 */
74246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1025, 0) }, /* CVP-303 */
74346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1026, 0) }, /* CVP-305 */
74446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1027, 0) }, /* CVP-307 */
74546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1028, 0) }, /* CVP-309 */
74646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1029, 0) }, /* CVP-309GP */
74746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x102a, 0) }, /* PSR-1500 */
74846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x102b, 0) }, /* PSR-3000 */
74946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x102e, 0) }, /* ELS-01/01C */
75046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1030, 0) }, /* PSR-295/293 */
75146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1031, 0) }, /* DGX-205/203 */
75246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1032, 0) }, /* DGX-305 */
75346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1033, 0) }, /* DGX-505 */
75446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1034, 0) }, /* NULL */
75546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1035, 0) }, /* NULL */
75646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1036, 0) }, /* NULL */
75746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1037, 0) }, /* NULL */
75846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1038, 0) }, /* NULL */
75946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1039, 0) }, /* NULL */
76046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x103a, 0) }, /* NULL */
76146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x103b, 0) }, /* NULL */
76246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x103c, 0) }, /* NULL */
76346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x103d, 0) }, /* NULL */
76446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x103e, 0) }, /* NULL */
76546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x103f, 0) }, /* NULL */
76646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1040, 0) }, /* NULL */
76746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1041, 0) }, /* NULL */
76846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1042, 0) }, /* NULL */
76946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1043, 0) }, /* NULL */
77046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1044, 0) }, /* NULL */
77146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1045, 0) }, /* NULL */
77246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x104e, 0) }, /* NULL */
77346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x104f, 0) }, /* NULL */
77446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1050, 0) }, /* NULL */
77546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1051, 0) }, /* NULL */
77646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1052, 0) }, /* NULL */
77746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1053, 0) }, /* NULL */
77846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1054, 0) }, /* NULL */
77946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1055, 0) }, /* NULL */
78046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1056, 0) }, /* NULL */
78146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1057, 0) }, /* NULL */
78246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1058, 0) }, /* NULL */
78346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1059, 0) }, /* NULL */
78446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x105a, 0) }, /* NULL */
78546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x105b, 0) }, /* NULL */
78646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x105c, 0) }, /* NULL */
78746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x105d, 0) }, /* NULL */
78846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1503, 3) }, /* MOX6/MOX8 */
78946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x2000, 0) }, /* DGP-7 */
79046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x2001, 0) }, /* DGP-5 */
79146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x2002, 0) }, /* NULL */
79246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x2003, 0) }, /* NULL */
79346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5000, 0) }, /* CS1D */
79446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5001, 0) }, /* DSP1D */
79546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5002, 0) }, /* DME32 */
79646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5003, 0) }, /* DM2000 */
79746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5004, 0) }, /* 02R96 */
79846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5005, 0) }, /* ACU16-C */
79946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5006, 0) }, /* NHB32-C */
80046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5007, 0) }, /* DM1000 */
80146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5008, 0) }, /* 01V96 */
80246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5009, 0) }, /* SPX2000 */
80346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x500a, 0) }, /* PM5D */
80446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x500b, 0) }, /* DME64N */
80546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x500c, 0) }, /* DME24N */
80646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x500d, 0) }, /* NULL */
80746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x500e, 0) }, /* NULL */
80846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x500f, 0) }, /* NULL */
80946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x7000, 0) }, /* DTX */
81046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x7010, 0) }, /* UB99 */
81146f0b27aSHans Petter Selasky };
81246f0b27aSHans Petter Selasky 
813f1a16106SHans Petter Selasky static const STRUCT_USB_HOST_ID __used uaudio_devs[] = {
814f1a16106SHans Petter Selasky 	/* Generic USB audio class match */
815f1a16106SHans Petter Selasky 	{USB_IFACE_CLASS(UICLASS_AUDIO),
816f1a16106SHans Petter Selasky 	 USB_IFACE_SUBCLASS(UISUBCLASS_AUDIOCONTROL),},
817f1a16106SHans Petter Selasky 	/* Generic USB MIDI class match */
818f1a16106SHans Petter Selasky 	{USB_IFACE_CLASS(UICLASS_AUDIO),
819f1a16106SHans Petter Selasky 	 USB_IFACE_SUBCLASS(UISUBCLASS_MIDISTREAM),},
820f1a16106SHans Petter Selasky };
821f1a16106SHans Petter Selasky 
8223a3f90c6SAndrew Thompson static int
8233a3f90c6SAndrew Thompson uaudio_probe(device_t dev)
8243a3f90c6SAndrew Thompson {
825760bc48eSAndrew Thompson 	struct usb_attach_arg *uaa = device_get_ivars(dev);
8263a3f90c6SAndrew Thompson 
827fadc970bSAndrew Thompson 	if (uaa->usb_mode != USB_MODE_HOST)
8283a3f90c6SAndrew Thompson 		return (ENXIO);
8293a3f90c6SAndrew Thompson 
83046f0b27aSHans Petter Selasky 	/* lookup non-standard device(s) */
83146f0b27aSHans Petter Selasky 
83246f0b27aSHans Petter Selasky 	if (usbd_lookup_id_by_uaa(uaudio_vendor_midi,
83346f0b27aSHans Petter Selasky 	    sizeof(uaudio_vendor_midi), uaa) == 0) {
83446f0b27aSHans Petter Selasky 		return (BUS_PROBE_SPECIFIC);
83546f0b27aSHans Petter Selasky 	}
8363a3f90c6SAndrew Thompson 
83725b74dabSHans Petter Selasky 	if (uaa->info.bInterfaceClass != UICLASS_AUDIO) {
83876eaf537SHans Petter Selasky 		if (uaa->info.bInterfaceClass != UICLASS_VENDOR ||
83976eaf537SHans Petter Selasky 		    usb_test_quirk(uaa, UQ_AU_VENDOR_CLASS) == 0)
84025b74dabSHans Petter Selasky 			return (ENXIO);
84125b74dabSHans Petter Selasky 	}
84225b74dabSHans Petter Selasky 
84325b74dabSHans Petter Selasky 	/* check for AUDIO control interface */
84425b74dabSHans Petter Selasky 
84525b74dabSHans Petter Selasky 	if (uaa->info.bInterfaceSubClass == UISUBCLASS_AUDIOCONTROL) {
846a593f6b8SAndrew Thompson 		if (usb_test_quirk(uaa, UQ_BAD_AUDIO))
8473a3f90c6SAndrew Thompson 			return (ENXIO);
8483a3f90c6SAndrew Thompson 		else
849cd10bffaSAndriy Gapon 			return (BUS_PROBE_GENERIC);
8503a3f90c6SAndrew Thompson 	}
851dc694251SAndrew Thompson 
852dc694251SAndrew Thompson 	/* check for MIDI stream */
853dc694251SAndrew Thompson 
85425b74dabSHans Petter Selasky 	if (uaa->info.bInterfaceSubClass == UISUBCLASS_MIDISTREAM) {
85525b74dabSHans Petter Selasky 		if (usb_test_quirk(uaa, UQ_BAD_MIDI))
85625b74dabSHans Petter Selasky 			return (ENXIO);
85725b74dabSHans Petter Selasky 		else
858cd10bffaSAndriy Gapon 			return (BUS_PROBE_GENERIC);
859dc694251SAndrew Thompson 	}
8603a3f90c6SAndrew Thompson 	return (ENXIO);
8613a3f90c6SAndrew Thompson }
8623a3f90c6SAndrew Thompson 
8633a3f90c6SAndrew Thompson static int
8643a3f90c6SAndrew Thompson uaudio_attach(device_t dev)
8653a3f90c6SAndrew Thompson {
866760bc48eSAndrew Thompson 	struct usb_attach_arg *uaa = device_get_ivars(dev);
8673a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(dev);
868760bc48eSAndrew Thompson 	struct usb_interface_descriptor *id;
86933da3daaSHans Petter Selasky 	usb_error_t err;
8703a3f90c6SAndrew Thompson 	device_t child;
8713a3f90c6SAndrew Thompson 
8723a3f90c6SAndrew Thompson 	sc->sc_play_chan.priv_sc = sc;
8733a3f90c6SAndrew Thompson 	sc->sc_rec_chan.priv_sc = sc;
8743a3f90c6SAndrew Thompson 	sc->sc_udev = uaa->device;
875b029f6bbSAndrew Thompson 	sc->sc_mixer_iface_index = uaa->info.bIfaceIndex;
876b029f6bbSAndrew Thompson 	sc->sc_mixer_iface_no = uaa->info.bIfaceNum;
877455a367fSHans Petter Selasky 	sc->sc_config_msg[0].hdr.pm_callback = &uaudio_configure_msg;
878455a367fSHans Petter Selasky 	sc->sc_config_msg[0].sc = sc;
879455a367fSHans Petter Selasky 	sc->sc_config_msg[1].hdr.pm_callback = &uaudio_configure_msg;
880455a367fSHans Petter Selasky 	sc->sc_config_msg[1].sc = sc;
8813a3f90c6SAndrew Thompson 
882a593f6b8SAndrew Thompson 	if (usb_test_quirk(uaa, UQ_AUDIO_SWAP_LR))
8833a3f90c6SAndrew Thompson 		sc->sc_uq_audio_swap_lr = 1;
8843a3f90c6SAndrew Thompson 
885a593f6b8SAndrew Thompson 	if (usb_test_quirk(uaa, UQ_AU_INP_ASYNC))
8863a3f90c6SAndrew Thompson 		sc->sc_uq_au_inp_async = 1;
8873a3f90c6SAndrew Thompson 
888a593f6b8SAndrew Thompson 	if (usb_test_quirk(uaa, UQ_AU_NO_XU))
8893a3f90c6SAndrew Thompson 		sc->sc_uq_au_no_xu = 1;
8903a3f90c6SAndrew Thompson 
891a593f6b8SAndrew Thompson 	if (usb_test_quirk(uaa, UQ_BAD_ADC))
8923a3f90c6SAndrew Thompson 		sc->sc_uq_bad_adc = 1;
8933a3f90c6SAndrew Thompson 
89425b74dabSHans Petter Selasky 	if (usb_test_quirk(uaa, UQ_AU_VENDOR_CLASS))
89525b74dabSHans Petter Selasky 		sc->sc_uq_au_vendor_class = 1;
89625b74dabSHans Petter Selasky 
8973a3f90c6SAndrew Thompson 	umidi_init(dev);
8983a3f90c6SAndrew Thompson 
899a593f6b8SAndrew Thompson 	device_set_usb_desc(dev);
9003a3f90c6SAndrew Thompson 
901a593f6b8SAndrew Thompson 	id = usbd_get_interface_descriptor(uaa->iface);
9023a3f90c6SAndrew Thompson 
903e2524b2eSHans Petter Selasky 	/* must fill mixer info before channel info */
9043a3f90c6SAndrew Thompson 	uaudio_mixer_fill_info(sc, uaa->device, id);
9053a3f90c6SAndrew Thompson 
906e2524b2eSHans Petter Selasky 	/* fill channel info */
907e2524b2eSHans Petter Selasky 	uaudio_chan_fill_info(sc, uaa->device);
908e2524b2eSHans Petter Selasky 
9093a3f90c6SAndrew Thompson 	DPRINTF("audio rev %d.%02x\n",
9103a3f90c6SAndrew Thompson 	    sc->sc_audio_rev >> 8,
9113a3f90c6SAndrew Thompson 	    sc->sc_audio_rev & 0xff);
9123a3f90c6SAndrew Thompson 
913ff4d5953SHans Petter Selasky 	if (sc->sc_mixer_count == 0) {
914ff4d5953SHans Petter Selasky 		if (uaa->info.idVendor == USB_VENDOR_MAUDIO &&
915ff4d5953SHans Petter Selasky 		    (uaa->info.idProduct == USB_PRODUCT_MAUDIO_FASTTRACKULTRA ||
916ff4d5953SHans Petter Selasky 		    uaa->info.idProduct == USB_PRODUCT_MAUDIO_FASTTRACKULTRA8R)) {
917ff4d5953SHans Petter Selasky 			DPRINTF("Generating mixer descriptors\n");
918ff4d5953SHans Petter Selasky 			uaudio_mixer_controls_create_ftu(sc);
919ff4d5953SHans Petter Selasky 		}
920ff4d5953SHans Petter Selasky 	}
921ff4d5953SHans Petter Selasky 
9223a3f90c6SAndrew Thompson 	DPRINTF("%d mixer controls\n",
9233a3f90c6SAndrew Thompson 	    sc->sc_mixer_count);
9243a3f90c6SAndrew Thompson 
925455a367fSHans Petter Selasky 	if (sc->sc_play_chan.num_alt > 0) {
926455a367fSHans Petter Selasky 		uint8_t x;
92733da3daaSHans Petter Selasky 
92833da3daaSHans Petter Selasky 		/*
92933da3daaSHans Petter Selasky 		 * Need to set a default alternate interface, else
93033da3daaSHans Petter Selasky 		 * some USB audio devices might go into an infinte
93133da3daaSHans Petter Selasky 		 * re-enumeration loop:
93233da3daaSHans Petter Selasky 		 */
93333da3daaSHans Petter Selasky 		err = usbd_set_alt_interface_index(sc->sc_udev,
93433da3daaSHans Petter Selasky 		    sc->sc_play_chan.usb_alt[0].iface_index,
93533da3daaSHans Petter Selasky 		    sc->sc_play_chan.usb_alt[0].iface_alt_index);
93633da3daaSHans Petter Selasky 		if (err) {
93733da3daaSHans Petter Selasky 			DPRINTF("setting of alternate index failed: %s!\n",
93833da3daaSHans Petter Selasky 			    usbd_errstr(err));
93933da3daaSHans Petter Selasky 		}
940455a367fSHans Petter Selasky 		for (x = 0; x != sc->sc_play_chan.num_alt; x++) {
941902514f6SHans Petter Selasky 			device_printf(dev, "Play: %d Hz, %d ch, %s format, "
942d3e08ca9SHans Petter Selasky 			    "2x8ms buffer.\n",
943455a367fSHans Petter Selasky 			    sc->sc_play_chan.usb_alt[x].sample_rate,
944455a367fSHans Petter Selasky 			    sc->sc_play_chan.usb_alt[x].channels,
945455a367fSHans Petter Selasky 			    sc->sc_play_chan.usb_alt[x].p_fmt->description);
946455a367fSHans Petter Selasky 		}
9473a3f90c6SAndrew Thompson 	} else {
9489b5da816SHans Petter Selasky 		device_printf(dev, "No playback.\n");
9493a3f90c6SAndrew Thompson 	}
9503a3f90c6SAndrew Thompson 
951455a367fSHans Petter Selasky 	if (sc->sc_rec_chan.num_alt > 0) {
952455a367fSHans Petter Selasky 		uint8_t x;
95333da3daaSHans Petter Selasky 
95433da3daaSHans Petter Selasky 		/*
95533da3daaSHans Petter Selasky 		 * Need to set a default alternate interface, else
95633da3daaSHans Petter Selasky 		 * some USB audio devices might go into an infinte
95733da3daaSHans Petter Selasky 		 * re-enumeration loop:
95833da3daaSHans Petter Selasky 		 */
95933da3daaSHans Petter Selasky 		err = usbd_set_alt_interface_index(sc->sc_udev,
96033da3daaSHans Petter Selasky 		    sc->sc_rec_chan.usb_alt[0].iface_index,
96133da3daaSHans Petter Selasky 		    sc->sc_rec_chan.usb_alt[0].iface_alt_index);
96233da3daaSHans Petter Selasky 		if (err) {
96333da3daaSHans Petter Selasky 			DPRINTF("setting of alternate index failed: %s!\n",
96433da3daaSHans Petter Selasky 			    usbd_errstr(err));
96533da3daaSHans Petter Selasky 		}
966455a367fSHans Petter Selasky 		for (x = 0; x != sc->sc_rec_chan.num_alt; x++) {
967902514f6SHans Petter Selasky 			device_printf(dev, "Record: %d Hz, %d ch, %s format, "
968d3e08ca9SHans Petter Selasky 			    "2x8ms buffer.\n",
969455a367fSHans Petter Selasky 			    sc->sc_rec_chan.usb_alt[x].sample_rate,
970455a367fSHans Petter Selasky 			    sc->sc_rec_chan.usb_alt[x].channels,
971455a367fSHans Petter Selasky 			    sc->sc_rec_chan.usb_alt[x].p_fmt->description);
972455a367fSHans Petter Selasky 		}
9733a3f90c6SAndrew Thompson 	} else {
9749b5da816SHans Petter Selasky 		device_printf(dev, "No recording.\n");
9753a3f90c6SAndrew Thompson 	}
9763a3f90c6SAndrew Thompson 
97746f0b27aSHans Petter Selasky 	if (sc->sc_midi_chan.valid == 0) {
97846f0b27aSHans Petter Selasky 		if (usbd_lookup_id_by_uaa(uaudio_vendor_midi,
97946f0b27aSHans Petter Selasky 		    sizeof(uaudio_vendor_midi), uaa) == 0) {
98046f0b27aSHans Petter Selasky 			sc->sc_midi_chan.iface_index =
98146f0b27aSHans Petter Selasky 			    (uint8_t)uaa->driver_info;
98246f0b27aSHans Petter Selasky 			sc->sc_midi_chan.iface_alt_index = 0;
98346f0b27aSHans Petter Selasky 			sc->sc_midi_chan.valid = 1;
98446f0b27aSHans Petter Selasky 		}
98546f0b27aSHans Petter Selasky 	}
98646f0b27aSHans Petter Selasky 
9873a3f90c6SAndrew Thompson 	if (sc->sc_midi_chan.valid) {
9883a3f90c6SAndrew Thompson 
9893a3f90c6SAndrew Thompson 		if (umidi_probe(dev)) {
9903a3f90c6SAndrew Thompson 			goto detach;
9913a3f90c6SAndrew Thompson 		}
9929b5da816SHans Petter Selasky 		device_printf(dev, "MIDI sequencer.\n");
9933a3f90c6SAndrew Thompson 	} else {
99476b71212SHans Petter Selasky 		device_printf(dev, "No MIDI sequencer.\n");
9953a3f90c6SAndrew Thompson 	}
9963a3f90c6SAndrew Thompson 
9973a3f90c6SAndrew Thompson 	DPRINTF("doing child attach\n");
9983a3f90c6SAndrew Thompson 
9993a3f90c6SAndrew Thompson 	/* attach the children */
10003a3f90c6SAndrew Thompson 
10013a3f90c6SAndrew Thompson 	sc->sc_sndcard_func.func = SCF_PCM;
10023a3f90c6SAndrew Thompson 
10039b5da816SHans Petter Selasky 	/*
10049b5da816SHans Petter Selasky 	 * Only attach a PCM device if we have a playback, recording
10059b5da816SHans Petter Selasky 	 * or mixer device present:
10069b5da816SHans Petter Selasky 	 */
1007455a367fSHans Petter Selasky 	if (sc->sc_play_chan.num_alt > 0 ||
1008455a367fSHans Petter Selasky 	    sc->sc_rec_chan.num_alt > 0 ||
10099b5da816SHans Petter Selasky 	    sc->sc_mix_info) {
10103a3f90c6SAndrew Thompson 		child = device_add_child(dev, "pcm", -1);
10113a3f90c6SAndrew Thompson 
10123a3f90c6SAndrew Thompson 		if (child == NULL) {
10133a3f90c6SAndrew Thompson 			DPRINTF("out of memory\n");
10143a3f90c6SAndrew Thompson 			goto detach;
10153a3f90c6SAndrew Thompson 		}
10163a3f90c6SAndrew Thompson 		device_set_ivars(child, &sc->sc_sndcard_func);
10179b5da816SHans Petter Selasky 	}
10183a3f90c6SAndrew Thompson 
10193a3f90c6SAndrew Thompson 	if (bus_generic_attach(dev)) {
10203a3f90c6SAndrew Thompson 		DPRINTF("child attach failed\n");
10213a3f90c6SAndrew Thompson 		goto detach;
10223a3f90c6SAndrew Thompson 	}
1023902514f6SHans Petter Selasky 
102476b71212SHans Petter Selasky 	if (uaudio_hid_probe(sc, uaa) == 0) {
102576b71212SHans Petter Selasky 		device_printf(dev, "HID volume keys found.\n");
102676b71212SHans Petter Selasky 	} else {
102776b71212SHans Petter Selasky 		device_printf(dev, "No HID volume keys found.\n");
102876b71212SHans Petter Selasky 	}
102976b71212SHans Petter Selasky 
1030902514f6SHans Petter Selasky 	/* reload all mixer settings */
1031902514f6SHans Petter Selasky 	uaudio_mixer_reload_all(sc);
1032902514f6SHans Petter Selasky 
10333a3f90c6SAndrew Thompson 	return (0);			/* success */
10343a3f90c6SAndrew Thompson 
10353a3f90c6SAndrew Thompson detach:
10363a3f90c6SAndrew Thompson 	uaudio_detach(dev);
10373a3f90c6SAndrew Thompson 	return (ENXIO);
10383a3f90c6SAndrew Thompson }
10393a3f90c6SAndrew Thompson 
10403a3f90c6SAndrew Thompson static void
10413a3f90c6SAndrew Thompson uaudio_pcm_setflags(device_t dev, uint32_t flags)
10423a3f90c6SAndrew Thompson {
10433a3f90c6SAndrew Thompson 	pcm_setflags(dev, pcm_getflags(dev) | flags);
10443a3f90c6SAndrew Thompson }
10453a3f90c6SAndrew Thompson 
10463a3f90c6SAndrew Thompson int
10473a3f90c6SAndrew Thompson uaudio_attach_sub(device_t dev, kobj_class_t mixer_class, kobj_class_t chan_class)
10483a3f90c6SAndrew Thompson {
10493a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(device_get_parent(dev));
10503a3f90c6SAndrew Thompson 	char status[SND_STATUSLEN];
10513a3f90c6SAndrew Thompson 
10523a3f90c6SAndrew Thompson 	uaudio_mixer_init(sc);
10533a3f90c6SAndrew Thompson 
10543a3f90c6SAndrew Thompson 	if (sc->sc_uq_audio_swap_lr) {
10553a3f90c6SAndrew Thompson 		DPRINTF("hardware has swapped left and right\n");
105690da2b28SAriff Abdullah 		/* uaudio_pcm_setflags(dev, SD_F_PSWAPLR); */
10573a3f90c6SAndrew Thompson 	}
10583a3f90c6SAndrew Thompson 	if (!(sc->sc_mix_info & SOUND_MASK_PCM)) {
10593a3f90c6SAndrew Thompson 
10603a3f90c6SAndrew Thompson 		DPRINTF("emulating master volume\n");
10613a3f90c6SAndrew Thompson 
10623a3f90c6SAndrew Thompson 		/*
10633a3f90c6SAndrew Thompson 		 * Emulate missing pcm mixer controller
10643a3f90c6SAndrew Thompson 		 * through FEEDER_VOLUME
10653a3f90c6SAndrew Thompson 		 */
10663a3f90c6SAndrew Thompson 		uaudio_pcm_setflags(dev, SD_F_SOFTPCMVOL);
10673a3f90c6SAndrew Thompson 	}
10689dd12733SHans Petter Selasky 	if (sc->sc_pcm_bitperfect) {
10699dd12733SHans Petter Selasky 		DPRINTF("device needs bitperfect by default\n");
10709dd12733SHans Petter Selasky 		uaudio_pcm_setflags(dev, SD_F_BITPERFECT);
10719dd12733SHans Petter Selasky 	}
1072902514f6SHans Petter Selasky 	if (mixer_init(dev, mixer_class, sc))
10733a3f90c6SAndrew Thompson 		goto detach;
10743a3f90c6SAndrew Thompson 	sc->sc_mixer_init = 1;
10753a3f90c6SAndrew Thompson 
10762ba0f361SHans Petter Selasky 	mixer_hwvol_init(dev);
10772ba0f361SHans Petter Selasky 
10783a3f90c6SAndrew Thompson 	snprintf(status, sizeof(status), "at ? %s", PCM_KLDSTRING(snd_uaudio));
10793a3f90c6SAndrew Thompson 
10803a3f90c6SAndrew Thompson 	if (pcm_register(dev, sc,
1081455a367fSHans Petter Selasky 	    (sc->sc_play_chan.num_alt > 0) ? 1 : 0,
1082455a367fSHans Petter Selasky 	    (sc->sc_rec_chan.num_alt > 0) ? 1 : 0)) {
10833a3f90c6SAndrew Thompson 		goto detach;
10843a3f90c6SAndrew Thompson 	}
108590da2b28SAriff Abdullah 
108690da2b28SAriff Abdullah 	uaudio_pcm_setflags(dev, SD_F_MPSAFE);
10873a3f90c6SAndrew Thompson 	sc->sc_pcm_registered = 1;
10883a3f90c6SAndrew Thompson 
1089455a367fSHans Petter Selasky 	if (sc->sc_play_chan.num_alt > 0) {
10903a3f90c6SAndrew Thompson 		pcm_addchan(dev, PCMDIR_PLAY, chan_class, sc);
10913a3f90c6SAndrew Thompson 	}
1092455a367fSHans Petter Selasky 	if (sc->sc_rec_chan.num_alt > 0) {
10933a3f90c6SAndrew Thompson 		pcm_addchan(dev, PCMDIR_REC, chan_class, sc);
10943a3f90c6SAndrew Thompson 	}
10953a3f90c6SAndrew Thompson 	pcm_setstatus(dev, status);
10963a3f90c6SAndrew Thompson 
1097902514f6SHans Petter Selasky 	uaudio_mixer_register_sysctl(sc, dev);
1098902514f6SHans Petter Selasky 
109985bad582SHans Petter Selasky 	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
110085bad582SHans Petter Selasky 	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
110185bad582SHans Petter Selasky 	    "feedback_rate", CTLFLAG_RD, &sc->sc_play_chan.feedback_rate,
110285bad582SHans Petter Selasky 	    0, "Feedback sample rate in Hz");
110385bad582SHans Petter Selasky 
11043a3f90c6SAndrew Thompson 	return (0);			/* success */
11053a3f90c6SAndrew Thompson 
11063a3f90c6SAndrew Thompson detach:
11073a3f90c6SAndrew Thompson 	uaudio_detach_sub(dev);
11083a3f90c6SAndrew Thompson 	return (ENXIO);
11093a3f90c6SAndrew Thompson }
11103a3f90c6SAndrew Thompson 
11113a3f90c6SAndrew Thompson int
11123a3f90c6SAndrew Thompson uaudio_detach_sub(device_t dev)
11133a3f90c6SAndrew Thompson {
11143a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(device_get_parent(dev));
11153a3f90c6SAndrew Thompson 	int error = 0;
11163a3f90c6SAndrew Thompson 
11173a3f90c6SAndrew Thompson repeat:
11183a3f90c6SAndrew Thompson 	if (sc->sc_pcm_registered) {
11193a3f90c6SAndrew Thompson 		error = pcm_unregister(dev);
11203a3f90c6SAndrew Thompson 	} else {
11213a3f90c6SAndrew Thompson 		if (sc->sc_mixer_init) {
11223a3f90c6SAndrew Thompson 			error = mixer_uninit(dev);
11233a3f90c6SAndrew Thompson 		}
11243a3f90c6SAndrew Thompson 	}
11253a3f90c6SAndrew Thompson 
11263a3f90c6SAndrew Thompson 	if (error) {
11273a3f90c6SAndrew Thompson 		device_printf(dev, "Waiting for sound application to exit!\n");
1128a593f6b8SAndrew Thompson 		usb_pause_mtx(NULL, 2 * hz);
11293a3f90c6SAndrew Thompson 		goto repeat;		/* try again */
11303a3f90c6SAndrew Thompson 	}
11313a3f90c6SAndrew Thompson 	return (0);			/* success */
11323a3f90c6SAndrew Thompson }
11333a3f90c6SAndrew Thompson 
11343a3f90c6SAndrew Thompson static int
11353a3f90c6SAndrew Thompson uaudio_detach(device_t dev)
11363a3f90c6SAndrew Thompson {
11373a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(dev);
11383a3f90c6SAndrew Thompson 
1139a6ed4937SHans Petter Selasky 	/*
1140a6ed4937SHans Petter Selasky 	 * Stop USB transfers early so that any audio applications
1141a6ed4937SHans Petter Selasky 	 * will time out and close opened /dev/dspX.Y device(s), if
1142a6ed4937SHans Petter Selasky 	 * any.
1143a6ed4937SHans Petter Selasky 	 */
1144455a367fSHans Petter Selasky 	usb_proc_explore_lock(sc->sc_udev);
1145455a367fSHans Petter Selasky 	sc->sc_play_chan.operation = CHAN_OP_DRAIN;
1146455a367fSHans Petter Selasky 	sc->sc_rec_chan.operation = CHAN_OP_DRAIN;
1147455a367fSHans Petter Selasky 	usb_proc_explore_mwait(sc->sc_udev,
1148455a367fSHans Petter Selasky 	    &sc->sc_config_msg[0], &sc->sc_config_msg[1]);
1149455a367fSHans Petter Selasky 	usb_proc_explore_unlock(sc->sc_udev);
1150455a367fSHans Petter Selasky 
1151b4380da7SHans Petter Selasky 	usbd_transfer_unsetup(sc->sc_play_chan.xfer, UAUDIO_NCHANBUFS + 1);
1152b4380da7SHans Petter Selasky 	usbd_transfer_unsetup(sc->sc_rec_chan.xfer, UAUDIO_NCHANBUFS + 1);
1153a6ed4937SHans Petter Selasky 
115476b71212SHans Petter Selasky 	uaudio_hid_detach(sc);
115576b71212SHans Petter Selasky 
1156a6ed4937SHans Petter Selasky 	if (bus_generic_detach(dev) != 0) {
11573a3f90c6SAndrew Thompson 		DPRINTF("detach failed!\n");
11583a3f90c6SAndrew Thompson 	}
11593a3f90c6SAndrew Thompson 	sbuf_delete(&sc->sc_sndstat);
11603a3f90c6SAndrew Thompson 	sc->sc_sndstat_valid = 0;
11613a3f90c6SAndrew Thompson 
11623a3f90c6SAndrew Thompson 	umidi_detach(dev);
11633a3f90c6SAndrew Thompson 
1164902514f6SHans Petter Selasky 	/* free mixer data */
1165902514f6SHans Petter Selasky 
1166902514f6SHans Petter Selasky 	uaudio_mixer_ctl_free(sc);
1167902514f6SHans Petter Selasky 
11683a3f90c6SAndrew Thompson 	return (0);
11693a3f90c6SAndrew Thompson }
11703a3f90c6SAndrew Thompson 
1171455a367fSHans Petter Selasky static uint32_t
1172455a367fSHans Petter Selasky uaudio_get_buffer_size(struct uaudio_chan *ch, uint8_t alt)
1173455a367fSHans Petter Selasky {
1174455a367fSHans Petter Selasky 	struct uaudio_chan_alt *chan_alt = &ch->usb_alt[alt];
1175455a367fSHans Petter Selasky 	/* We use 2 times 8ms of buffer */
1176455a367fSHans Petter Selasky 	uint32_t buf_size = (((chan_alt->sample_rate * (UAUDIO_NFRAMES / 8)) +
1177455a367fSHans Petter Selasky 	    1000 - 1) / 1000) * chan_alt->sample_size;
1178455a367fSHans Petter Selasky 	return (buf_size);
1179455a367fSHans Petter Selasky }
1180455a367fSHans Petter Selasky 
1181455a367fSHans Petter Selasky static void
1182455a367fSHans Petter Selasky uaudio_configure_msg_sub(struct uaudio_softc *sc,
1183455a367fSHans Petter Selasky     struct uaudio_chan *chan, int dir)
1184455a367fSHans Petter Selasky {
1185455a367fSHans Petter Selasky 	struct uaudio_chan_alt *chan_alt;
1186455a367fSHans Petter Selasky 	uint32_t frames;
1187455a367fSHans Petter Selasky 	uint32_t buf_size;
1188455a367fSHans Petter Selasky 	uint16_t fps;
1189455a367fSHans Petter Selasky 	uint8_t set_alt;
1190455a367fSHans Petter Selasky 	uint8_t fps_shift;
1191455a367fSHans Petter Selasky 	uint8_t operation;
1192455a367fSHans Petter Selasky 	usb_error_t err;
1193455a367fSHans Petter Selasky 
1194455a367fSHans Petter Selasky 	if (chan->num_alt <= 0)
1195455a367fSHans Petter Selasky 		return;
1196455a367fSHans Petter Selasky 
1197455a367fSHans Petter Selasky 	DPRINTF("\n");
1198455a367fSHans Petter Selasky 
1199455a367fSHans Petter Selasky 	usb_proc_explore_lock(sc->sc_udev);
1200455a367fSHans Petter Selasky 	operation = chan->operation;
1201455a367fSHans Petter Selasky 	chan->operation = CHAN_OP_NONE;
1202455a367fSHans Petter Selasky 	usb_proc_explore_unlock(sc->sc_udev);
1203455a367fSHans Petter Selasky 
1204455a367fSHans Petter Selasky 	mtx_lock(chan->pcm_mtx);
1205455a367fSHans Petter Selasky 	if (chan->cur_alt != chan->set_alt)
1206455a367fSHans Petter Selasky 		set_alt = chan->set_alt;
1207455a367fSHans Petter Selasky 	else
1208455a367fSHans Petter Selasky 		set_alt = CHAN_MAX_ALT;
1209455a367fSHans Petter Selasky 	mtx_unlock(chan->pcm_mtx);
1210455a367fSHans Petter Selasky 
1211455a367fSHans Petter Selasky 	if (set_alt >= chan->num_alt)
1212455a367fSHans Petter Selasky 		goto done;
1213455a367fSHans Petter Selasky 
1214455a367fSHans Petter Selasky 	chan_alt = chan->usb_alt + set_alt;
1215455a367fSHans Petter Selasky 
1216455a367fSHans Petter Selasky 	usbd_transfer_unsetup(chan->xfer, UAUDIO_NCHANBUFS + 1);
1217455a367fSHans Petter Selasky 
1218455a367fSHans Petter Selasky 	err = usbd_set_alt_interface_index(sc->sc_udev,
1219455a367fSHans Petter Selasky 	    chan_alt->iface_index, chan_alt->iface_alt_index);
1220455a367fSHans Petter Selasky 	if (err) {
1221455a367fSHans Petter Selasky 		DPRINTF("setting of alternate index failed: %s!\n",
1222455a367fSHans Petter Selasky 		    usbd_errstr(err));
1223455a367fSHans Petter Selasky 		goto error;
1224455a367fSHans Petter Selasky 	}
1225455a367fSHans Petter Selasky 
1226455a367fSHans Petter Selasky 	/*
1227455a367fSHans Petter Selasky 	 * Only set the sample rate if the channel reports that it
1228455a367fSHans Petter Selasky 	 * supports the frequency control.
1229455a367fSHans Petter Selasky 	 */
1230455a367fSHans Petter Selasky 
1231455a367fSHans Petter Selasky 	if (sc->sc_audio_rev >= UAUDIO_VERSION_30) {
1232455a367fSHans Petter Selasky 		/* FALLTHROUGH */
1233455a367fSHans Petter Selasky 	} else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) {
1234455a367fSHans Petter Selasky 		unsigned int x;
1235455a367fSHans Petter Selasky 
1236455a367fSHans Petter Selasky 		for (x = 0; x != 256; x++) {
1237455a367fSHans Petter Selasky 			if (dir == PCMDIR_PLAY) {
1238455a367fSHans Petter Selasky 				if (!(sc->sc_mixer_clocks.bit_output[x / 8] &
1239455a367fSHans Petter Selasky 				    (1 << (x % 8)))) {
1240455a367fSHans Petter Selasky 					continue;
1241455a367fSHans Petter Selasky 				}
1242455a367fSHans Petter Selasky 			} else {
1243455a367fSHans Petter Selasky 				if (!(sc->sc_mixer_clocks.bit_input[x / 8] &
1244455a367fSHans Petter Selasky 				    (1 << (x % 8)))) {
1245455a367fSHans Petter Selasky 					continue;
1246455a367fSHans Petter Selasky 				}
1247455a367fSHans Petter Selasky 			}
1248455a367fSHans Petter Selasky 
1249455a367fSHans Petter Selasky 			if (uaudio20_set_speed(sc->sc_udev,
1250455a367fSHans Petter Selasky 			    sc->sc_mixer_iface_no, x, chan_alt->sample_rate)) {
1251455a367fSHans Petter Selasky 				/*
1252455a367fSHans Petter Selasky 				 * If the endpoint is adaptive setting
1253455a367fSHans Petter Selasky 				 * the speed may fail.
1254455a367fSHans Petter Selasky 				 */
1255455a367fSHans Petter Selasky 				DPRINTF("setting of sample rate failed! "
1256455a367fSHans Petter Selasky 				    "(continuing anyway)\n");
1257455a367fSHans Petter Selasky 			}
1258455a367fSHans Petter Selasky 		}
1259455a367fSHans Petter Selasky 	} else if (chan_alt->p_sed.v1->bmAttributes & UA_SED_FREQ_CONTROL) {
1260455a367fSHans Petter Selasky 		if (uaudio_set_speed(sc->sc_udev,
1261455a367fSHans Petter Selasky 		    chan_alt->p_ed1->bEndpointAddress, chan_alt->sample_rate)) {
1262455a367fSHans Petter Selasky 			/*
1263455a367fSHans Petter Selasky 			 * If the endpoint is adaptive setting the
1264455a367fSHans Petter Selasky 			 * speed may fail.
1265455a367fSHans Petter Selasky 			 */
1266455a367fSHans Petter Selasky 			DPRINTF("setting of sample rate failed! "
1267455a367fSHans Petter Selasky 			    "(continuing anyway)\n");
1268455a367fSHans Petter Selasky 		}
1269455a367fSHans Petter Selasky 	}
1270455a367fSHans Petter Selasky 	if (usbd_transfer_setup(sc->sc_udev, &chan_alt->iface_index, chan->xfer,
1271455a367fSHans Petter Selasky 	    chan_alt->usb_cfg, UAUDIO_NCHANBUFS + 1, chan, chan->pcm_mtx)) {
1272455a367fSHans Petter Selasky 		DPRINTF("could not allocate USB transfers!\n");
1273455a367fSHans Petter Selasky 		goto error;
1274455a367fSHans Petter Selasky 	}
1275455a367fSHans Petter Selasky 
1276455a367fSHans Petter Selasky 	fps = usbd_get_isoc_fps(sc->sc_udev);
1277455a367fSHans Petter Selasky 
1278455a367fSHans Petter Selasky 	if (fps < 8000) {
1279455a367fSHans Petter Selasky 		/* FULL speed USB */
1280455a367fSHans Petter Selasky 		frames = 8;
1281455a367fSHans Petter Selasky 	} else {
1282455a367fSHans Petter Selasky 		/* HIGH speed USB */
1283455a367fSHans Petter Selasky 		frames = UAUDIO_NFRAMES;
1284455a367fSHans Petter Selasky 	}
1285455a367fSHans Petter Selasky 
1286455a367fSHans Petter Selasky 	fps_shift = usbd_xfer_get_fps_shift(chan->xfer[0]);
1287455a367fSHans Petter Selasky 
1288455a367fSHans Petter Selasky 	/* down shift number of frames per second, if any */
1289455a367fSHans Petter Selasky 	fps >>= fps_shift;
1290455a367fSHans Petter Selasky 	frames >>= fps_shift;
1291455a367fSHans Petter Selasky 
1292455a367fSHans Petter Selasky 	/* bytes per frame should not be zero */
1293455a367fSHans Petter Selasky 	chan->bytes_per_frame[0] =
1294455a367fSHans Petter Selasky 	    ((chan_alt->sample_rate / fps) * chan_alt->sample_size);
1295455a367fSHans Petter Selasky 	chan->bytes_per_frame[1] =
1296455a367fSHans Petter Selasky 	    (((chan_alt->sample_rate + fps - 1) / fps) * chan_alt->sample_size);
1297455a367fSHans Petter Selasky 
1298455a367fSHans Petter Selasky 	/* setup data rate dithering, if any */
1299455a367fSHans Petter Selasky 	chan->frames_per_second = fps;
1300455a367fSHans Petter Selasky 	chan->sample_rem = chan_alt->sample_rate % fps;
1301455a367fSHans Petter Selasky 	chan->sample_curr = 0;
1302455a367fSHans Petter Selasky 
1303455a367fSHans Petter Selasky 	/* compute required buffer size */
1304455a367fSHans Petter Selasky 	buf_size = (chan->bytes_per_frame[1] * frames);
1305455a367fSHans Petter Selasky 
1306455a367fSHans Petter Selasky 	if (buf_size > (chan->end - chan->start)) {
1307455a367fSHans Petter Selasky 		DPRINTF("buffer size is too big\n");
1308455a367fSHans Petter Selasky 		goto error;
1309455a367fSHans Petter Selasky 	}
1310455a367fSHans Petter Selasky 
1311455a367fSHans Petter Selasky 	chan->intr_frames = frames;
1312455a367fSHans Petter Selasky 
1313455a367fSHans Petter Selasky 	DPRINTF("fps=%d sample_rem=%d\n", (int)fps, (int)chan->sample_rem);
1314455a367fSHans Petter Selasky 
1315455a367fSHans Petter Selasky 	if (chan->intr_frames == 0) {
1316455a367fSHans Petter Selasky 		DPRINTF("frame shift is too high!\n");
1317455a367fSHans Petter Selasky 		goto error;
1318455a367fSHans Petter Selasky 	}
1319455a367fSHans Petter Selasky 
1320455a367fSHans Petter Selasky 	mtx_lock(chan->pcm_mtx);
1321455a367fSHans Petter Selasky 	chan->cur_alt = set_alt;
1322455a367fSHans Petter Selasky 	mtx_unlock(chan->pcm_mtx);
1323455a367fSHans Petter Selasky 
1324455a367fSHans Petter Selasky done:
1325455a367fSHans Petter Selasky #if (UAUDIO_NCHANBUFS != 2)
1326455a367fSHans Petter Selasky #error "please update code"
1327455a367fSHans Petter Selasky #endif
1328455a367fSHans Petter Selasky 	switch (operation) {
1329455a367fSHans Petter Selasky 	case CHAN_OP_START:
1330455a367fSHans Petter Selasky 		mtx_lock(chan->pcm_mtx);
1331455a367fSHans Petter Selasky 		usbd_transfer_start(chan->xfer[0]);
1332455a367fSHans Petter Selasky 		usbd_transfer_start(chan->xfer[1]);
1333455a367fSHans Petter Selasky 		mtx_unlock(chan->pcm_mtx);
1334455a367fSHans Petter Selasky 		break;
1335455a367fSHans Petter Selasky 	case CHAN_OP_STOP:
1336455a367fSHans Petter Selasky 		mtx_lock(chan->pcm_mtx);
1337455a367fSHans Petter Selasky 		usbd_transfer_stop(chan->xfer[0]);
1338455a367fSHans Petter Selasky 		usbd_transfer_stop(chan->xfer[1]);
1339455a367fSHans Petter Selasky 		mtx_unlock(chan->pcm_mtx);
1340455a367fSHans Petter Selasky 		break;
1341455a367fSHans Petter Selasky 	default:
1342455a367fSHans Petter Selasky 		break;
1343455a367fSHans Petter Selasky 	}
1344455a367fSHans Petter Selasky 	return;
1345455a367fSHans Petter Selasky 
1346455a367fSHans Petter Selasky error:
1347455a367fSHans Petter Selasky 	usbd_transfer_unsetup(chan->xfer, UAUDIO_NCHANBUFS + 1);
1348455a367fSHans Petter Selasky 
1349455a367fSHans Petter Selasky 	mtx_lock(chan->pcm_mtx);
1350455a367fSHans Petter Selasky 	chan->cur_alt = CHAN_MAX_ALT;
1351455a367fSHans Petter Selasky 	mtx_unlock(chan->pcm_mtx);
1352455a367fSHans Petter Selasky }
1353455a367fSHans Petter Selasky 
1354455a367fSHans Petter Selasky static void
1355455a367fSHans Petter Selasky uaudio_configure_msg(struct usb_proc_msg *pm)
1356455a367fSHans Petter Selasky {
1357455a367fSHans Petter Selasky 	struct uaudio_softc *sc = ((struct uaudio_configure_msg *)pm)->sc;
1358455a367fSHans Petter Selasky 
1359455a367fSHans Petter Selasky 	usb_proc_explore_unlock(sc->sc_udev);
1360455a367fSHans Petter Selasky 	uaudio_configure_msg_sub(sc, &sc->sc_play_chan, PCMDIR_PLAY);
1361455a367fSHans Petter Selasky 	uaudio_configure_msg_sub(sc, &sc->sc_rec_chan, PCMDIR_REC);
1362455a367fSHans Petter Selasky 	usb_proc_explore_lock(sc->sc_udev);
1363455a367fSHans Petter Selasky }
1364455a367fSHans Petter Selasky 
13653a3f90c6SAndrew Thompson /*========================================================================*
13663a3f90c6SAndrew Thompson  * AS - Audio Stream - routines
13673a3f90c6SAndrew Thompson  *========================================================================*/
13683a3f90c6SAndrew Thompson 
1369b850ecc1SAndrew Thompson #ifdef USB_DEBUG
13703a3f90c6SAndrew Thompson static void
13714c21be9bSRebecca Cran uaudio_chan_dump_ep_desc(const usb_endpoint_descriptor_audio_t *ed)
13723a3f90c6SAndrew Thompson {
13733a3f90c6SAndrew Thompson 	if (ed) {
13743a3f90c6SAndrew Thompson 		DPRINTF("endpoint=%p bLength=%d bDescriptorType=%d \n"
13753a3f90c6SAndrew Thompson 		    "bEndpointAddress=%d bmAttributes=0x%x \n"
13763a3f90c6SAndrew Thompson 		    "wMaxPacketSize=%d bInterval=%d \n"
13773a3f90c6SAndrew Thompson 		    "bRefresh=%d bSynchAddress=%d\n",
13783a3f90c6SAndrew Thompson 		    ed, ed->bLength, ed->bDescriptorType,
13793a3f90c6SAndrew Thompson 		    ed->bEndpointAddress, ed->bmAttributes,
13803a3f90c6SAndrew Thompson 		    UGETW(ed->wMaxPacketSize), ed->bInterval,
1381e3e05e50SAndrew Thompson 		    UEP_HAS_REFRESH(ed) ? ed->bRefresh : 0,
1382e3e05e50SAndrew Thompson 		    UEP_HAS_SYNCADDR(ed) ? ed->bSynchAddress : 0);
13833a3f90c6SAndrew Thompson 	}
13843a3f90c6SAndrew Thompson }
13853a3f90c6SAndrew Thompson 
13863a3f90c6SAndrew Thompson #endif
13873a3f90c6SAndrew Thompson 
1388f895cc0cSHans Petter Selasky /*
1389f895cc0cSHans Petter Selasky  * The following is a workaround for broken no-name USB audio devices
1390f895cc0cSHans Petter Selasky  * sold by dealextreme called "3D sound". The problem is that the
1391f895cc0cSHans Petter Selasky  * manufacturer computed wMaxPacketSize is too small to hold the
1392f895cc0cSHans Petter Selasky  * actual data sent. In other words the device sometimes sends more
1393f895cc0cSHans Petter Selasky  * data than it actually reports it can send in a single isochronous
1394f895cc0cSHans Petter Selasky  * packet.
1395f895cc0cSHans Petter Selasky  */
1396f895cc0cSHans Petter Selasky static void
1397f895cc0cSHans Petter Selasky uaudio_record_fix_fs(usb_endpoint_descriptor_audio_t *ep,
1398f895cc0cSHans Petter Selasky     uint32_t xps, uint32_t add)
1399f895cc0cSHans Petter Selasky {
1400f895cc0cSHans Petter Selasky 	uint32_t mps;
1401f895cc0cSHans Petter Selasky 
1402f895cc0cSHans Petter Selasky 	mps = UGETW(ep->wMaxPacketSize);
1403f895cc0cSHans Petter Selasky 
1404f895cc0cSHans Petter Selasky 	/*
1405f895cc0cSHans Petter Selasky 	 * If the device indicates it can send more data than what the
1406f895cc0cSHans Petter Selasky 	 * sample rate indicates, we apply the workaround.
1407f895cc0cSHans Petter Selasky 	 */
1408f895cc0cSHans Petter Selasky 	if (mps > xps) {
1409f895cc0cSHans Petter Selasky 
1410f895cc0cSHans Petter Selasky 		/* allow additional data */
1411f895cc0cSHans Petter Selasky 		xps += add;
1412f895cc0cSHans Petter Selasky 
1413f895cc0cSHans Petter Selasky 		/* check against the maximum USB 1.x length */
1414f895cc0cSHans Petter Selasky 		if (xps > 1023)
1415f895cc0cSHans Petter Selasky 			xps = 1023;
1416f895cc0cSHans Petter Selasky 
1417f895cc0cSHans Petter Selasky 		/* check if we should do an update */
1418f895cc0cSHans Petter Selasky 		if (mps < xps) {
1419f895cc0cSHans Petter Selasky 			/* simply update the wMaxPacketSize field */
1420f895cc0cSHans Petter Selasky 			USETW(ep->wMaxPacketSize, xps);
1421f895cc0cSHans Petter Selasky 			DPRINTF("Workaround: Updated wMaxPacketSize "
1422f895cc0cSHans Petter Selasky 			    "from %d to %d bytes.\n",
1423f895cc0cSHans Petter Selasky 			    (int)mps, (int)xps);
1424f895cc0cSHans Petter Selasky 		}
1425f895cc0cSHans Petter Selasky 	}
1426f895cc0cSHans Petter Selasky }
1427f895cc0cSHans Petter Selasky 
1428e2524b2eSHans Petter Selasky static usb_error_t
1429e2524b2eSHans Petter Selasky uaudio20_check_rate(struct usb_device *udev, uint8_t iface_no,
1430e2524b2eSHans Petter Selasky     uint8_t clockid, uint32_t rate)
1431e2524b2eSHans Petter Selasky {
1432e2524b2eSHans Petter Selasky 	struct usb_device_request req;
1433e2524b2eSHans Petter Selasky 	usb_error_t error;
1434e2524b2eSHans Petter Selasky 	uint8_t data[255];
1435e2524b2eSHans Petter Selasky 	uint16_t actlen;
1436e2524b2eSHans Petter Selasky 	uint16_t rates;
1437e2524b2eSHans Petter Selasky 	uint16_t x;
1438e2524b2eSHans Petter Selasky 
1439e2524b2eSHans Petter Selasky 	DPRINTFN(6, "ifaceno=%d clockid=%d rate=%u\n",
1440e2524b2eSHans Petter Selasky 	    iface_no, clockid, rate);
1441e2524b2eSHans Petter Selasky 
1442e2524b2eSHans Petter Selasky 	req.bmRequestType = UT_READ_CLASS_INTERFACE;
1443e2524b2eSHans Petter Selasky 	req.bRequest = UA20_CS_RANGE;
1444e2524b2eSHans Petter Selasky 	USETW2(req.wValue, UA20_CS_SAM_FREQ_CONTROL, 0);
1445e2524b2eSHans Petter Selasky 	USETW2(req.wIndex, clockid, iface_no);
1446e2524b2eSHans Petter Selasky 	USETW(req.wLength, 255);
1447e2524b2eSHans Petter Selasky 
1448e2524b2eSHans Petter Selasky         error = usbd_do_request_flags(udev, NULL, &req, data,
1449e2524b2eSHans Petter Selasky 	    USB_SHORT_XFER_OK, &actlen, USB_DEFAULT_TIMEOUT);
1450e2524b2eSHans Petter Selasky 
1451e2524b2eSHans Petter Selasky 	if (error != 0 || actlen < 2)
1452e2524b2eSHans Petter Selasky 		return (USB_ERR_INVAL);
1453e2524b2eSHans Petter Selasky 
1454e2524b2eSHans Petter Selasky 	rates = data[0] | (data[1] << 8);
1455e2524b2eSHans Petter Selasky 	actlen = (actlen - 2) / 12;
1456e2524b2eSHans Petter Selasky 
1457e2524b2eSHans Petter Selasky 	if (rates > actlen) {
1458e2524b2eSHans Petter Selasky 		DPRINTF("Too many rates\n");
1459e2524b2eSHans Petter Selasky 		rates = actlen;
1460e2524b2eSHans Petter Selasky 	}
1461e2524b2eSHans Petter Selasky 
1462e2524b2eSHans Petter Selasky 	for (x = 0; x != rates; x++) {
1463e2524b2eSHans Petter Selasky 		uint32_t min = UGETDW(data + 2 + (12 * x));
1464e2524b2eSHans Petter Selasky 		uint32_t max = UGETDW(data + 6 + (12 * x));
1465e2524b2eSHans Petter Selasky 		uint32_t res = UGETDW(data + 10 + (12 * x));
1466e2524b2eSHans Petter Selasky 
1467e2524b2eSHans Petter Selasky 		if (res == 0) {
1468e2524b2eSHans Petter Selasky 			DPRINTF("Zero residue\n");
1469e2524b2eSHans Petter Selasky 			res = 1;
1470e2524b2eSHans Petter Selasky 		}
1471e2524b2eSHans Petter Selasky 
1472e2524b2eSHans Petter Selasky 		if (min > max) {
1473e2524b2eSHans Petter Selasky 			DPRINTF("Swapped max and min\n");
1474e2524b2eSHans Petter Selasky 			uint32_t temp;
1475e2524b2eSHans Petter Selasky 			temp = min;
1476e2524b2eSHans Petter Selasky 			min = max;
1477e2524b2eSHans Petter Selasky 			max = temp;
1478e2524b2eSHans Petter Selasky 		}
1479e2524b2eSHans Petter Selasky 
1480e2524b2eSHans Petter Selasky 		if (rate >= min && rate <= max &&
1481e2524b2eSHans Petter Selasky 		    (((rate - min) % res) == 0)) {
1482e2524b2eSHans Petter Selasky 			return (0);
1483e2524b2eSHans Petter Selasky 		}
1484e2524b2eSHans Petter Selasky 	}
1485e2524b2eSHans Petter Selasky 	return (USB_ERR_INVAL);
1486e2524b2eSHans Petter Selasky }
1487e2524b2eSHans Petter Selasky 
14883a3f90c6SAndrew Thompson static void
1489760bc48eSAndrew Thompson uaudio_chan_fill_info_sub(struct uaudio_softc *sc, struct usb_device *udev,
1490afbfddd9SAndrew Thompson     uint32_t rate, uint8_t channels, uint8_t bit_resolution)
14913a3f90c6SAndrew Thompson {
1492760bc48eSAndrew Thompson 	struct usb_descriptor *desc = NULL;
1493e2524b2eSHans Petter Selasky 	union uaudio_asid asid = { NULL };
1494e2524b2eSHans Petter Selasky 	union uaudio_asf1d asf1d = { NULL };
1495e2524b2eSHans Petter Selasky 	union uaudio_sed sed = { NULL };
14968bf51ab5SHans Petter Selasky 	struct usb_midi_streaming_endpoint_descriptor *msid = NULL;
1497f895cc0cSHans Petter Selasky 	usb_endpoint_descriptor_audio_t *ed1 = NULL;
1498e2524b2eSHans Petter Selasky 	const struct usb_audio_control_descriptor *acdp = NULL;
1499a593f6b8SAndrew Thompson 	struct usb_config_descriptor *cd = usbd_get_config_descriptor(udev);
1500760bc48eSAndrew Thompson 	struct usb_interface_descriptor *id;
1501e2524b2eSHans Petter Selasky 	const struct uaudio_format *p_fmt = NULL;
15023a3f90c6SAndrew Thompson 	struct uaudio_chan *chan;
1503455a367fSHans Petter Selasky 	struct uaudio_chan_alt *chan_alt;
1504455a367fSHans Petter Selasky 	uint32_t format;
15053a3f90c6SAndrew Thompson 	uint16_t curidx = 0xFFFF;
15063a3f90c6SAndrew Thompson 	uint16_t lastidx = 0xFFFF;
15073a3f90c6SAndrew Thompson 	uint16_t alt_index = 0;
1508e2524b2eSHans Petter Selasky 	uint16_t audio_rev = 0;
1509e2524b2eSHans Petter Selasky 	uint16_t x;
15103a3f90c6SAndrew Thompson 	uint8_t ep_dir;
15113a3f90c6SAndrew Thompson 	uint8_t bChannels;
15123a3f90c6SAndrew Thompson 	uint8_t bBitResolution;
15133a3f90c6SAndrew Thompson 	uint8_t audio_if = 0;
15148bf51ab5SHans Petter Selasky 	uint8_t midi_if = 0;
151525b74dabSHans Petter Selasky 	uint8_t uma_if_class;
15163a3f90c6SAndrew Thompson 
1517a593f6b8SAndrew Thompson 	while ((desc = usb_desc_foreach(cd, desc))) {
15183a3f90c6SAndrew Thompson 
15193a3f90c6SAndrew Thompson 		if ((desc->bDescriptorType == UDESC_INTERFACE) &&
15203a3f90c6SAndrew Thompson 		    (desc->bLength >= sizeof(*id))) {
15213a3f90c6SAndrew Thompson 
15223a3f90c6SAndrew Thompson 			id = (void *)desc;
15233a3f90c6SAndrew Thompson 
15243a3f90c6SAndrew Thompson 			if (id->bInterfaceNumber != lastidx) {
15253a3f90c6SAndrew Thompson 				lastidx = id->bInterfaceNumber;
15263a3f90c6SAndrew Thompson 				curidx++;
15273a3f90c6SAndrew Thompson 				alt_index = 0;
15283a3f90c6SAndrew Thompson 
15293a3f90c6SAndrew Thompson 			} else {
15303a3f90c6SAndrew Thompson 				alt_index++;
15313a3f90c6SAndrew Thompson 			}
15323a3f90c6SAndrew Thompson 
153376b71212SHans Petter Selasky 			if ((!(sc->sc_hid.flags & UAUDIO_HID_VALID)) &&
153476b71212SHans Petter Selasky 			    (id->bInterfaceClass == UICLASS_HID) &&
153576b71212SHans Petter Selasky 			    (id->bInterfaceSubClass == 0) &&
153676b71212SHans Petter Selasky 			    (id->bInterfaceProtocol == 0) &&
153776b71212SHans Petter Selasky 			    (alt_index == 0) &&
153876b71212SHans Petter Selasky 			    usbd_get_iface(udev, curidx) != NULL) {
153976b71212SHans Petter Selasky 				DPRINTF("Found HID interface at %d\n",
154076b71212SHans Petter Selasky 				    curidx);
154176b71212SHans Petter Selasky 				sc->sc_hid.flags |= UAUDIO_HID_VALID;
154276b71212SHans Petter Selasky 				sc->sc_hid.iface_index = curidx;
154376b71212SHans Petter Selasky 			}
154476b71212SHans Petter Selasky 
154525b74dabSHans Petter Selasky 			uma_if_class =
154625b74dabSHans Petter Selasky 			    ((id->bInterfaceClass == UICLASS_AUDIO) ||
154725b74dabSHans Petter Selasky 			    ((id->bInterfaceClass == UICLASS_VENDOR) &&
154825b74dabSHans Petter Selasky 			    (sc->sc_uq_au_vendor_class != 0)));
154925b74dabSHans Petter Selasky 
15508bf51ab5SHans Petter Selasky 			if ((uma_if_class != 0) &&
15518bf51ab5SHans Petter Selasky 			    (id->bInterfaceSubClass == UISUBCLASS_AUDIOSTREAM)) {
15523a3f90c6SAndrew Thompson 				audio_if = 1;
15533a3f90c6SAndrew Thompson 			} else {
15543a3f90c6SAndrew Thompson 				audio_if = 0;
15553a3f90c6SAndrew Thompson 			}
15563a3f90c6SAndrew Thompson 
155725b74dabSHans Petter Selasky 			if ((uma_if_class != 0) &&
15583a3f90c6SAndrew Thompson 			    (id->bInterfaceSubClass == UISUBCLASS_MIDISTREAM)) {
15593a3f90c6SAndrew Thompson 
15603a3f90c6SAndrew Thompson 				/*
15613a3f90c6SAndrew Thompson 				 * XXX could allow multiple MIDI interfaces
15623a3f90c6SAndrew Thompson 				 */
15638bf51ab5SHans Petter Selasky 				midi_if = 1;
15643a3f90c6SAndrew Thompson 
15653a3f90c6SAndrew Thompson 				if ((sc->sc_midi_chan.valid == 0) &&
15668bf51ab5SHans Petter Selasky 				    (usbd_get_iface(udev, curidx) != NULL)) {
15673a3f90c6SAndrew Thompson 					sc->sc_midi_chan.iface_index = curidx;
15683a3f90c6SAndrew Thompson 					sc->sc_midi_chan.iface_alt_index = alt_index;
15693a3f90c6SAndrew Thompson 					sc->sc_midi_chan.valid = 1;
15703a3f90c6SAndrew Thompson 				}
15718bf51ab5SHans Petter Selasky 			} else {
15728bf51ab5SHans Petter Selasky 				midi_if = 0;
15733a3f90c6SAndrew Thompson 			}
1574e2524b2eSHans Petter Selasky 			asid.v1 = NULL;
1575e2524b2eSHans Petter Selasky 			asf1d.v1 = NULL;
15763a3f90c6SAndrew Thompson 			ed1 = NULL;
1577e2524b2eSHans Petter Selasky 			sed.v1 = NULL;
15788c20de92SHans Petter Selasky 
15798c20de92SHans Petter Selasky 			/*
15808c20de92SHans Petter Selasky 			 * There can only be one USB audio instance
15818c20de92SHans Petter Selasky 			 * per USB device. Grab all USB audio
15828c20de92SHans Petter Selasky 			 * interfaces on this USB device so that we
15838c20de92SHans Petter Selasky 			 * don't attach USB audio twice:
15848c20de92SHans Petter Selasky 			 */
15858c20de92SHans Petter Selasky 			if (alt_index == 0 && curidx != sc->sc_mixer_iface_index &&
15868c20de92SHans Petter Selasky 			    (id->bInterfaceClass == UICLASS_AUDIO || audio_if != 0 ||
15878c20de92SHans Petter Selasky 			    midi_if != 0)) {
15888c20de92SHans Petter Selasky 				usbd_set_parent_iface(sc->sc_udev, curidx,
15898c20de92SHans Petter Selasky 				    sc->sc_mixer_iface_index);
15908c20de92SHans Petter Selasky 			}
15913a3f90c6SAndrew Thompson 		}
1592e2524b2eSHans Petter Selasky 
15931234097eSHans Petter Selasky 		if (audio_if == 0) {
15948bf51ab5SHans Petter Selasky 			if (midi_if == 0) {
1595e2524b2eSHans Petter Selasky 				if ((acdp == NULL) &&
1596e2524b2eSHans Petter Selasky 				    (desc->bDescriptorType == UDESC_CS_INTERFACE) &&
1597e2524b2eSHans Petter Selasky 				    (desc->bDescriptorSubtype == UDESCSUB_AC_HEADER) &&
1598e2524b2eSHans Petter Selasky 				    (desc->bLength >= sizeof(*acdp))) {
1599e2524b2eSHans Petter Selasky 					acdp = (void *)desc;
1600e2524b2eSHans Petter Selasky 					audio_rev = UGETW(acdp->bcdADC);
1601e2524b2eSHans Petter Selasky 				}
16028bf51ab5SHans Petter Selasky 			} else {
16038bf51ab5SHans Petter Selasky 				msid = (void *)desc;
1604e2524b2eSHans Petter Selasky 
16058bf51ab5SHans Petter Selasky 				/* get the maximum number of embedded jacks in use, if any */
16068bf51ab5SHans Petter Selasky 				if (msid->bLength >= sizeof(*msid) &&
16078bf51ab5SHans Petter Selasky 				    msid->bDescriptorType == UDESC_CS_ENDPOINT &&
16088bf51ab5SHans Petter Selasky 				    msid->bDescriptorSubtype == MS_GENERAL &&
16098bf51ab5SHans Petter Selasky 				    msid->bNumEmbMIDIJack > sc->sc_midi_chan.max_emb_jack) {
16108bf51ab5SHans Petter Selasky 					sc->sc_midi_chan.max_emb_jack = msid->bNumEmbMIDIJack;
16118bf51ab5SHans Petter Selasky 				}
16128bf51ab5SHans Petter Selasky 			}
16131234097eSHans Petter Selasky 			/*
16141234097eSHans Petter Selasky 			 * Don't collect any USB audio descriptors if
16151234097eSHans Petter Selasky 			 * this is not an USB audio stream interface.
16161234097eSHans Petter Selasky 			 */
16171234097eSHans Petter Selasky 			continue;
16181234097eSHans Petter Selasky 		}
16191234097eSHans Petter Selasky 
1620e2524b2eSHans Petter Selasky 		if ((acdp != NULL) &&
1621e2524b2eSHans Petter Selasky 		    (desc->bDescriptorType == UDESC_CS_INTERFACE) &&
1622e2524b2eSHans Petter Selasky 		    (desc->bDescriptorSubtype == AS_GENERAL) &&
1623e2524b2eSHans Petter Selasky 		    (asid.v1 == NULL)) {
1624e2524b2eSHans Petter Selasky 			if (audio_rev >= UAUDIO_VERSION_30) {
1625e2524b2eSHans Petter Selasky 				/* FALLTHROUGH */
1626e2524b2eSHans Petter Selasky 			} else if (audio_rev >= UAUDIO_VERSION_20) {
1627e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*asid.v2)) {
1628e2524b2eSHans Petter Selasky 					asid.v2 = (void *)desc;
1629e2524b2eSHans Petter Selasky 				}
1630e2524b2eSHans Petter Selasky 			} else {
1631e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*asid.v1)) {
1632e2524b2eSHans Petter Selasky 					asid.v1 = (void *)desc;
16333a3f90c6SAndrew Thompson 				}
16343a3f90c6SAndrew Thompson 			}
1635e2524b2eSHans Petter Selasky 		}
1636e2524b2eSHans Petter Selasky 		if ((acdp != NULL) &&
1637e2524b2eSHans Petter Selasky 		    (desc->bDescriptorType == UDESC_CS_INTERFACE) &&
16383a3f90c6SAndrew Thompson 		    (desc->bDescriptorSubtype == FORMAT_TYPE) &&
1639e2524b2eSHans Petter Selasky 		    (asf1d.v1 == NULL)) {
1640e2524b2eSHans Petter Selasky 			if (audio_rev >= UAUDIO_VERSION_30) {
1641e2524b2eSHans Petter Selasky 				/* FALLTHROUGH */
1642e2524b2eSHans Petter Selasky 			} else if (audio_rev >= UAUDIO_VERSION_20) {
1643e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*asf1d.v2))
1644e2524b2eSHans Petter Selasky 					asf1d.v2 = (void *)desc;
1645e2524b2eSHans Petter Selasky 			} else {
1646e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*asf1d.v1)) {
1647e2524b2eSHans Petter Selasky 					asf1d.v1 = (void *)desc;
1648e2524b2eSHans Petter Selasky 
1649e2524b2eSHans Petter Selasky 					if (asf1d.v1->bFormatType != FORMAT_TYPE_I) {
16503a3f90c6SAndrew Thompson 						DPRINTFN(11, "ignored bFormatType = %d\n",
1651e2524b2eSHans Petter Selasky 						    asf1d.v1->bFormatType);
1652e2524b2eSHans Petter Selasky 						asf1d.v1 = NULL;
16533a3f90c6SAndrew Thompson 						continue;
16543a3f90c6SAndrew Thompson 					}
1655e2524b2eSHans Petter Selasky 					if (desc->bLength < (sizeof(*asf1d.v1) +
1656e2524b2eSHans Petter Selasky 					    ((asf1d.v1->bSamFreqType == 0) ? 6 :
1657e2524b2eSHans Petter Selasky 					    (asf1d.v1->bSamFreqType * 3)))) {
1658e2524b2eSHans Petter Selasky 						DPRINTFN(11, "invalid descriptor, "
1659e2524b2eSHans Petter Selasky 						    "too short\n");
1660e2524b2eSHans Petter Selasky 						asf1d.v1 = NULL;
16613a3f90c6SAndrew Thompson 						continue;
16623a3f90c6SAndrew Thompson 					}
16633a3f90c6SAndrew Thompson 				}
16643a3f90c6SAndrew Thompson 			}
1665e2524b2eSHans Petter Selasky 		}
16663a3f90c6SAndrew Thompson 		if ((desc->bDescriptorType == UDESC_ENDPOINT) &&
1667e2524b2eSHans Petter Selasky 		    (desc->bLength >= UEP_MINSIZE) &&
1668e2524b2eSHans Petter Selasky 		    (ed1 == NULL)) {
16693a3f90c6SAndrew Thompson 			ed1 = (void *)desc;
16703a3f90c6SAndrew Thompson 			if (UE_GET_XFERTYPE(ed1->bmAttributes) != UE_ISOCHRONOUS) {
16713a3f90c6SAndrew Thompson 				ed1 = NULL;
1672e2524b2eSHans Petter Selasky 				continue;
16733a3f90c6SAndrew Thompson 			}
16743a3f90c6SAndrew Thompson 		}
1675e2524b2eSHans Petter Selasky 		if ((acdp != NULL) &&
1676e2524b2eSHans Petter Selasky 		    (desc->bDescriptorType == UDESC_CS_ENDPOINT) &&
16773a3f90c6SAndrew Thompson 		    (desc->bDescriptorSubtype == AS_GENERAL) &&
1678e2524b2eSHans Petter Selasky 		    (sed.v1 == NULL)) {
1679e2524b2eSHans Petter Selasky 			if (audio_rev >= UAUDIO_VERSION_30) {
1680e2524b2eSHans Petter Selasky 				/* FALLTHROUGH */
1681e2524b2eSHans Petter Selasky 			} else if (audio_rev >= UAUDIO_VERSION_20) {
1682e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*sed.v2))
1683e2524b2eSHans Petter Selasky 					sed.v2 = (void *)desc;
1684e2524b2eSHans Petter Selasky 			} else {
1685e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*sed.v1))
1686e2524b2eSHans Petter Selasky 					sed.v1 = (void *)desc;
16873a3f90c6SAndrew Thompson 			}
16883a3f90c6SAndrew Thompson 		}
16891234097eSHans Petter Selasky 		if (asid.v1 == NULL || asf1d.v1 == NULL ||
16901234097eSHans Petter Selasky 		    ed1 == NULL || sed.v1 == NULL) {
1691e2524b2eSHans Petter Selasky 			/* need more descriptors */
1692e2524b2eSHans Petter Selasky 			continue;
1693e2524b2eSHans Petter Selasky 		}
16943a3f90c6SAndrew Thompson 
16953a3f90c6SAndrew Thompson 		ep_dir = UE_GET_DIR(ed1->bEndpointAddress);
16963a3f90c6SAndrew Thompson 
1697e3e05e50SAndrew Thompson 		/* We ignore sync endpoint information until further. */
16983a3f90c6SAndrew Thompson 
1699e2524b2eSHans Petter Selasky 		if (audio_rev >= UAUDIO_VERSION_30) {
1700e2524b2eSHans Petter Selasky 			goto next_ep;
1701e2524b2eSHans Petter Selasky 		} else if (audio_rev >= UAUDIO_VERSION_20) {
17023a3f90c6SAndrew Thompson 
1703e2524b2eSHans Petter Selasky 			uint32_t dwFormat;
17043a3f90c6SAndrew Thompson 
1705e2524b2eSHans Petter Selasky 			dwFormat = UGETDW(asid.v2->bmFormats);
1706e2524b2eSHans Petter Selasky 			bChannels = asid.v2->bNrChannels;
1707f09566d3SHans Petter Selasky 			bBitResolution = asf1d.v2->bSubslotSize * 8;
1708e2524b2eSHans Petter Selasky 
1709e2524b2eSHans Petter Selasky 			if ((bChannels != channels) ||
1710e2524b2eSHans Petter Selasky 			    (bBitResolution != bit_resolution)) {
1711e2524b2eSHans Petter Selasky 				DPRINTF("Wrong number of channels\n");
1712e2524b2eSHans Petter Selasky 				goto next_ep;
1713e2524b2eSHans Petter Selasky 			}
1714e2524b2eSHans Petter Selasky 
1715e2524b2eSHans Petter Selasky 			for (p_fmt = uaudio20_formats;
1716e2524b2eSHans Petter Selasky 			    p_fmt->wFormat != 0; p_fmt++) {
1717e2524b2eSHans Petter Selasky 				if ((p_fmt->wFormat & dwFormat) &&
1718e2524b2eSHans Petter Selasky 				    (p_fmt->bPrecision == bBitResolution))
1719e2524b2eSHans Petter Selasky 					break;
1720e2524b2eSHans Petter Selasky 			}
1721e2524b2eSHans Petter Selasky 
1722e2524b2eSHans Petter Selasky 			if (p_fmt->wFormat == 0) {
1723e2524b2eSHans Petter Selasky 				DPRINTF("Unsupported audio format\n");
1724e2524b2eSHans Petter Selasky 				goto next_ep;
1725e2524b2eSHans Petter Selasky 			}
1726e2524b2eSHans Petter Selasky 
1727e2524b2eSHans Petter Selasky 			for (x = 0; x != 256; x++) {
1728e2524b2eSHans Petter Selasky 				if (ep_dir == UE_DIR_OUT) {
1729e2524b2eSHans Petter Selasky 					if (!(sc->sc_mixer_clocks.bit_output[x / 8] &
1730e2524b2eSHans Petter Selasky 					    (1 << (x % 8)))) {
1731e2524b2eSHans Petter Selasky 						continue;
17323a3f90c6SAndrew Thompson 					}
17333a3f90c6SAndrew Thompson 				} else {
1734e2524b2eSHans Petter Selasky 					if (!(sc->sc_mixer_clocks.bit_input[x / 8] &
1735e2524b2eSHans Petter Selasky 					    (1 << (x % 8)))) {
1736e2524b2eSHans Petter Selasky 						continue;
1737e2524b2eSHans Petter Selasky 					}
1738e2524b2eSHans Petter Selasky 				}
17393a3f90c6SAndrew Thompson 
1740e2524b2eSHans Petter Selasky 				DPRINTF("Checking clock ID=%d\n", x);
1741e2524b2eSHans Petter Selasky 
1742e2524b2eSHans Petter Selasky 				if (uaudio20_check_rate(udev,
1743e2524b2eSHans Petter Selasky 				    sc->sc_mixer_iface_no, x, rate)) {
1744e2524b2eSHans Petter Selasky 					DPRINTF("Unsupported sampling "
1745e2524b2eSHans Petter Selasky 					    "rate, id=%d\n", x);
1746e2524b2eSHans Petter Selasky 					goto next_ep;
1747e2524b2eSHans Petter Selasky 				}
1748e2524b2eSHans Petter Selasky 			}
1749e2524b2eSHans Petter Selasky 		} else {
1750e2524b2eSHans Petter Selasky 			uint16_t wFormat;
1751e2524b2eSHans Petter Selasky 
1752e2524b2eSHans Petter Selasky 			wFormat = UGETW(asid.v1->wFormatTag);
1753e2524b2eSHans Petter Selasky 			bChannels = UAUDIO_MAX_CHAN(asf1d.v1->bNrChannels);
1754f09566d3SHans Petter Selasky 			bBitResolution = asf1d.v1->bSubFrameSize * 8;
1755e2524b2eSHans Petter Selasky 
1756e2524b2eSHans Petter Selasky 			if (asf1d.v1->bSamFreqType == 0) {
1757e2524b2eSHans Petter Selasky 				DPRINTFN(16, "Sample rate: %d-%dHz\n",
1758e2524b2eSHans Petter Selasky 				    UA_SAMP_LO(asf1d.v1),
1759e2524b2eSHans Petter Selasky 				    UA_SAMP_HI(asf1d.v1));
1760e2524b2eSHans Petter Selasky 
1761e2524b2eSHans Petter Selasky 				if ((rate >= UA_SAMP_LO(asf1d.v1)) &&
1762e2524b2eSHans Petter Selasky 				    (rate <= UA_SAMP_HI(asf1d.v1)))
1763e2524b2eSHans Petter Selasky 					goto found_rate;
1764e2524b2eSHans Petter Selasky 			} else {
1765e2524b2eSHans Petter Selasky 
1766e2524b2eSHans Petter Selasky 				for (x = 0; x < asf1d.v1->bSamFreqType; x++) {
17673a3f90c6SAndrew Thompson 					DPRINTFN(16, "Sample rate = %dHz\n",
1768e2524b2eSHans Petter Selasky 					    UA_GETSAMP(asf1d.v1, x));
17693a3f90c6SAndrew Thompson 
1770e2524b2eSHans Petter Selasky 					if (rate == UA_GETSAMP(asf1d.v1, x))
17713a3f90c6SAndrew Thompson 						goto found_rate;
17723a3f90c6SAndrew Thompson 				}
17733a3f90c6SAndrew Thompson 			}
1774e2524b2eSHans Petter Selasky 			goto next_ep;
17753a3f90c6SAndrew Thompson 
17763a3f90c6SAndrew Thompson 	found_rate:
1777e2524b2eSHans Petter Selasky 			for (p_fmt = uaudio10_formats;
1778e2524b2eSHans Petter Selasky 			    p_fmt->wFormat != 0; p_fmt++) {
17793a3f90c6SAndrew Thompson 				if ((p_fmt->wFormat == wFormat) &&
1780e2524b2eSHans Petter Selasky 				    (p_fmt->bPrecision == bBitResolution))
1781e2524b2eSHans Petter Selasky 					break;
17823a3f90c6SAndrew Thompson 			}
1783e2524b2eSHans Petter Selasky 			if (p_fmt->wFormat == 0) {
1784e2524b2eSHans Petter Selasky 				DPRINTF("Unsupported audio format\n");
1785e2524b2eSHans Petter Selasky 				goto next_ep;
17863a3f90c6SAndrew Thompson 			}
17873a3f90c6SAndrew Thompson 
1788e2524b2eSHans Petter Selasky 			if ((bChannels != channels) ||
1789e2524b2eSHans Petter Selasky 			    (bBitResolution != bit_resolution)) {
1790e2524b2eSHans Petter Selasky 				DPRINTF("Wrong number of channels\n");
1791e2524b2eSHans Petter Selasky 				goto next_ep;
1792e2524b2eSHans Petter Selasky 			}
1793e2524b2eSHans Petter Selasky 		}
17943a3f90c6SAndrew Thompson 
17953a3f90c6SAndrew Thompson 		chan = (ep_dir == UE_DIR_IN) ?
1796e2524b2eSHans Petter Selasky 		    &sc->sc_rec_chan : &sc->sc_play_chan;
17973a3f90c6SAndrew Thompson 
1798455a367fSHans Petter Selasky 		if (usbd_get_iface(udev, curidx) == NULL) {
1799455a367fSHans Petter Selasky 			DPRINTF("Interface is not valid\n");
1800e2524b2eSHans Petter Selasky 			goto next_ep;
1801e2524b2eSHans Petter Selasky 		}
1802455a367fSHans Petter Selasky 		if (chan->num_alt == CHAN_MAX_ALT) {
1803455a367fSHans Petter Selasky 			DPRINTF("Too many alternate settings\n");
1804455a367fSHans Petter Selasky 			goto next_ep;
1805455a367fSHans Petter Selasky 		}
1806455a367fSHans Petter Selasky 		chan->set_alt = 0;
1807455a367fSHans Petter Selasky 		chan->cur_alt = CHAN_MAX_ALT;
18083a3f90c6SAndrew Thompson 
1809455a367fSHans Petter Selasky 		chan_alt = &chan->usb_alt[chan->num_alt++];
1810455a367fSHans Petter Selasky 
1811b850ecc1SAndrew Thompson #ifdef USB_DEBUG
18123a3f90c6SAndrew Thompson 		uaudio_chan_dump_ep_desc(ed1);
18133a3f90c6SAndrew Thompson #endif
18143a3f90c6SAndrew Thompson 		DPRINTF("Sample rate = %dHz, channels = %d, "
18153a3f90c6SAndrew Thompson 		    "bits = %d, format = %s\n", rate, channels,
18163a3f90c6SAndrew Thompson 		    bit_resolution, p_fmt->description);
18173a3f90c6SAndrew Thompson 
1818455a367fSHans Petter Selasky 		chan_alt->sample_rate = rate;
1819455a367fSHans Petter Selasky 		chan_alt->p_asf1d = asf1d;
1820455a367fSHans Petter Selasky 		chan_alt->p_ed1 = ed1;
1821455a367fSHans Petter Selasky 		chan_alt->p_fmt = p_fmt;
1822455a367fSHans Petter Selasky 		chan_alt->p_sed = sed;
1823455a367fSHans Petter Selasky 		chan_alt->iface_index = curidx;
1824455a367fSHans Petter Selasky 		chan_alt->iface_alt_index = alt_index;
1825455a367fSHans Petter Selasky 
18263a3f90c6SAndrew Thompson 		if (ep_dir == UE_DIR_IN)
1827455a367fSHans Petter Selasky 			chan_alt->usb_cfg = uaudio_cfg_record;
18283a3f90c6SAndrew Thompson 		else
1829455a367fSHans Petter Selasky 			chan_alt->usb_cfg = uaudio_cfg_play;
18303a3f90c6SAndrew Thompson 
1831455a367fSHans Petter Selasky 		chan_alt->sample_size = (UAUDIO_MAX_CHAN(channels) *
1832e2524b2eSHans Petter Selasky 		    p_fmt->bPrecision) / 8;
1833455a367fSHans Petter Selasky 		chan_alt->channels = channels;
18343a3f90c6SAndrew Thompson 
1835f895cc0cSHans Petter Selasky 		if (ep_dir == UE_DIR_IN &&
1836f895cc0cSHans Petter Selasky 		    usbd_get_speed(udev) == USB_SPEED_FULL) {
1837f895cc0cSHans Petter Selasky 			uaudio_record_fix_fs(ed1,
1838455a367fSHans Petter Selasky 			    chan_alt->sample_size * (rate / 1000),
1839455a367fSHans Petter Selasky 			    chan_alt->sample_size * (rate / 4000));
1840f895cc0cSHans Petter Selasky 		}
1841f895cc0cSHans Petter Selasky 
1842455a367fSHans Petter Selasky 		/* setup play/record format */
1843455a367fSHans Petter Selasky 
1844455a367fSHans Petter Selasky 		format = chan_alt->p_fmt->freebsd_fmt;
1845455a367fSHans Petter Selasky 
18469dd12733SHans Petter Selasky 		/* get default SND_FORMAT() */
18479dd12733SHans Petter Selasky 		format = SND_FORMAT(format, chan_alt->channels, 0);
18489dd12733SHans Petter Selasky 
1849455a367fSHans Petter Selasky 		switch (chan_alt->channels) {
18509dd12733SHans Petter Selasky 		uint32_t temp_fmt;
1851455a367fSHans Petter Selasky 		case 1:
18529dd12733SHans Petter Selasky 		case 2:
18539dd12733SHans Petter Selasky 			/* mono and stereo */
1854455a367fSHans Petter Selasky 			break;
1855455a367fSHans Petter Selasky 		default:
1856455a367fSHans Petter Selasky 			/* surround and more */
18579dd12733SHans Petter Selasky 			temp_fmt = feeder_matrix_default_format(format);
18589dd12733SHans Petter Selasky 			/* if multichannel, then format can be zero */
18599dd12733SHans Petter Selasky 			if (temp_fmt != 0)
18609dd12733SHans Petter Selasky 				format = temp_fmt;
1861455a367fSHans Petter Selasky 			break;
1862455a367fSHans Petter Selasky 		}
1863455a367fSHans Petter Selasky 
1864455a367fSHans Petter Selasky 		/* check if format is not supported */
1865455a367fSHans Petter Selasky 		if (format == 0) {
1866455a367fSHans Petter Selasky 			DPRINTF("The selected audio format is not supported\n");
1867455a367fSHans Petter Selasky 			chan->num_alt--;
1868455a367fSHans Petter Selasky 			goto next_ep;
1869455a367fSHans Petter Selasky 		}
1870ffae621eSHans Petter Selasky 		if (chan->num_alt > 1) {
1871455a367fSHans Petter Selasky 			/* we only accumulate one format at different sample rates */
1872ffae621eSHans Petter Selasky 			if (chan->pcm_format[0] != format) {
1873455a367fSHans Petter Selasky 				DPRINTF("Multiple formats is not supported\n");
1874455a367fSHans Petter Selasky 				chan->num_alt--;
1875455a367fSHans Petter Selasky 				goto next_ep;
1876455a367fSHans Petter Selasky 			}
1877ffae621eSHans Petter Selasky 			/* ignore if duplicate sample rate entry */
1878ffae621eSHans Petter Selasky 			if (rate == chan->usb_alt[chan->num_alt - 2].sample_rate) {
1879ffae621eSHans Petter Selasky 				DPRINTF("Duplicate sample rate detected\n");
1880ffae621eSHans Petter Selasky 				chan->num_alt--;
1881ffae621eSHans Petter Selasky 				goto next_ep;
1882ffae621eSHans Petter Selasky 			}
1883ffae621eSHans Petter Selasky 		}
1884455a367fSHans Petter Selasky 		chan->pcm_cap.fmtlist = chan->pcm_format;
1885455a367fSHans Petter Selasky 		chan->pcm_cap.fmtlist[0] = format;
1886455a367fSHans Petter Selasky 
18879dd12733SHans Petter Selasky 		/* check if device needs bitperfect */
18889dd12733SHans Petter Selasky 		if (chan_alt->channels > UAUDIO_MATRIX_MAX)
18899dd12733SHans Petter Selasky 			sc->sc_pcm_bitperfect = 1;
18909dd12733SHans Petter Selasky 
1891455a367fSHans Petter Selasky 		if (rate < chan->pcm_cap.minspeed || chan->pcm_cap.minspeed == 0)
1892455a367fSHans Petter Selasky 			chan->pcm_cap.minspeed = rate;
1893455a367fSHans Petter Selasky 		if (rate > chan->pcm_cap.maxspeed || chan->pcm_cap.maxspeed == 0)
1894455a367fSHans Petter Selasky 			chan->pcm_cap.maxspeed = rate;
1895455a367fSHans Petter Selasky 
1896e2524b2eSHans Petter Selasky 		if (sc->sc_sndstat_valid != 0) {
18973a3f90c6SAndrew Thompson 			sbuf_printf(&sc->sc_sndstat, "\n\t"
1898e2524b2eSHans Petter Selasky 			    "mode %d.%d:(%s) %dch, %dbit, %s, %dHz",
18993a3f90c6SAndrew Thompson 			    curidx, alt_index,
19003a3f90c6SAndrew Thompson 			    (ep_dir == UE_DIR_IN) ? "input" : "output",
1901e2524b2eSHans Petter Selasky 				    channels, p_fmt->bPrecision,
19023a3f90c6SAndrew Thompson 				    p_fmt->description, rate);
19033a3f90c6SAndrew Thompson 		}
1904e2524b2eSHans Petter Selasky 
1905e2524b2eSHans Petter Selasky 	next_ep:
1906e2524b2eSHans Petter Selasky 		sed.v1 = NULL;
1907e2524b2eSHans Petter Selasky 		ed1 = NULL;
19083a3f90c6SAndrew Thompson 	}
19093a3f90c6SAndrew Thompson }
19103a3f90c6SAndrew Thompson 
1911afbfddd9SAndrew Thompson /* This structure defines all the supported rates. */
1912afbfddd9SAndrew Thompson 
1913455a367fSHans Petter Selasky static const uint32_t uaudio_rate_list[CHAN_MAX_ALT] = {
19142c2752d3SHans Petter Selasky 	384000,
19152c2752d3SHans Petter Selasky 	352800,
19162c2752d3SHans Petter Selasky 	192000,
19172c2752d3SHans Petter Selasky 	176400,
1918afbfddd9SAndrew Thompson 	96000,
1919455a367fSHans Petter Selasky 	88200,
1920afbfddd9SAndrew Thompson 	88000,
1921afbfddd9SAndrew Thompson 	80000,
1922afbfddd9SAndrew Thompson 	72000,
1923afbfddd9SAndrew Thompson 	64000,
1924afbfddd9SAndrew Thompson 	56000,
1925afbfddd9SAndrew Thompson 	48000,
1926afbfddd9SAndrew Thompson 	44100,
1927afbfddd9SAndrew Thompson 	40000,
1928afbfddd9SAndrew Thompson 	32000,
1929afbfddd9SAndrew Thompson 	24000,
1930afbfddd9SAndrew Thompson 	22050,
1931afbfddd9SAndrew Thompson 	16000,
1932afbfddd9SAndrew Thompson 	11025,
1933afbfddd9SAndrew Thompson 	8000,
1934afbfddd9SAndrew Thompson 	0
1935afbfddd9SAndrew Thompson };
1936afbfddd9SAndrew Thompson 
19373a3f90c6SAndrew Thompson static void
1938760bc48eSAndrew Thompson uaudio_chan_fill_info(struct uaudio_softc *sc, struct usb_device *udev)
19393a3f90c6SAndrew Thompson {
19403a3f90c6SAndrew Thompson 	uint32_t rate = uaudio_default_rate;
1941afbfddd9SAndrew Thompson 	uint8_t z;
19423a3f90c6SAndrew Thompson 	uint8_t bits = uaudio_default_bits;
19433a3f90c6SAndrew Thompson 	uint8_t y;
19443a3f90c6SAndrew Thompson 	uint8_t channels = uaudio_default_channels;
19453a3f90c6SAndrew Thompson 	uint8_t x;
19463a3f90c6SAndrew Thompson 
19473a3f90c6SAndrew Thompson 	bits -= (bits % 8);
19483a3f90c6SAndrew Thompson 	if ((bits == 0) || (bits > 32)) {
19493a3f90c6SAndrew Thompson 		/* set a valid value */
19503a3f90c6SAndrew Thompson 		bits = 32;
19513a3f90c6SAndrew Thompson 	}
1952afbfddd9SAndrew Thompson 	if (channels == 0) {
1953afbfddd9SAndrew Thompson 		switch (usbd_get_speed(udev)) {
1954afbfddd9SAndrew Thompson 		case USB_SPEED_LOW:
1955afbfddd9SAndrew Thompson 		case USB_SPEED_FULL:
1956afbfddd9SAndrew Thompson 			/*
1957afbfddd9SAndrew Thompson 			 * Due to high bandwidth usage and problems
1958afbfddd9SAndrew Thompson 			 * with HIGH-speed split transactions we
1959afbfddd9SAndrew Thompson 			 * disable surround setups on FULL-speed USB
1960afbfddd9SAndrew Thompson 			 * by default
1961afbfddd9SAndrew Thompson 			 */
19621234097eSHans Petter Selasky 			channels = 4;
1963afbfddd9SAndrew Thompson 			break;
1964afbfddd9SAndrew Thompson 		default:
19659dd12733SHans Petter Selasky 			channels = UAUDIO_CHANNELS_MAX;
1966afbfddd9SAndrew Thompson 			break;
1967afbfddd9SAndrew Thompson 		}
19689dd12733SHans Petter Selasky 	} else if (channels > UAUDIO_CHANNELS_MAX)
19699dd12733SHans Petter Selasky 		channels = UAUDIO_CHANNELS_MAX;
19709dd12733SHans Petter Selasky 
19719dd12733SHans Petter Selasky 	if (sbuf_new(&sc->sc_sndstat, NULL, 4096, SBUF_AUTOEXTEND))
19723a3f90c6SAndrew Thompson 		sc->sc_sndstat_valid = 1;
19739dd12733SHans Petter Selasky 
19743a3f90c6SAndrew Thompson 	/* try to search for a valid config */
19753a3f90c6SAndrew Thompson 
19763a3f90c6SAndrew Thompson 	for (x = channels; x; x--) {
19773a3f90c6SAndrew Thompson 		for (y = bits; y; y -= 8) {
1978afbfddd9SAndrew Thompson 
1979afbfddd9SAndrew Thompson 			/* try user defined rate, if any */
1980afbfddd9SAndrew Thompson 			if (rate != 0)
1981afbfddd9SAndrew Thompson 				uaudio_chan_fill_info_sub(sc, udev, rate, x, y);
1982afbfddd9SAndrew Thompson 
1983afbfddd9SAndrew Thompson 			/* try find a matching rate, if any */
1984455a367fSHans Petter Selasky 			for (z = 0; uaudio_rate_list[z]; z++)
1985afbfddd9SAndrew Thompson 				uaudio_chan_fill_info_sub(sc, udev, uaudio_rate_list[z], x, y);
19863a3f90c6SAndrew Thompson 		}
19873a3f90c6SAndrew Thompson 	}
1988455a367fSHans Petter Selasky 	if (sc->sc_sndstat_valid)
19893a3f90c6SAndrew Thompson 		sbuf_finish(&sc->sc_sndstat);
19903a3f90c6SAndrew Thompson }
19913a3f90c6SAndrew Thompson 
19923a3f90c6SAndrew Thompson static void
1993b4380da7SHans Petter Selasky uaudio_chan_play_sync_callback(struct usb_xfer *xfer, usb_error_t error)
1994b4380da7SHans Petter Selasky {
1995b4380da7SHans Petter Selasky 	struct uaudio_chan *ch = usbd_xfer_softc(xfer);
1996b4380da7SHans Petter Selasky 	struct usb_page_cache *pc;
199785bad582SHans Petter Selasky 	uint64_t sample_rate;
1998b4380da7SHans Petter Selasky 	uint8_t buf[4];
1999b4380da7SHans Petter Selasky 	uint64_t temp;
2000b4380da7SHans Petter Selasky 	int len;
2001b4380da7SHans Petter Selasky 	int actlen;
2002b4380da7SHans Petter Selasky 	int nframes;
2003b4380da7SHans Petter Selasky 
2004b4380da7SHans Petter Selasky 	usbd_xfer_status(xfer, &actlen, NULL, NULL, &nframes);
2005b4380da7SHans Petter Selasky 
2006b4380da7SHans Petter Selasky 	switch (USB_GET_STATE(xfer)) {
2007b4380da7SHans Petter Selasky 	case USB_ST_TRANSFERRED:
2008b4380da7SHans Petter Selasky 
2009b4380da7SHans Petter Selasky 		DPRINTFN(6, "transferred %d bytes\n", actlen);
2010b4380da7SHans Petter Selasky 
2011b4380da7SHans Petter Selasky 		if (nframes == 0)
2012b4380da7SHans Petter Selasky 			break;
2013b4380da7SHans Petter Selasky 		len = usbd_xfer_frame_len(xfer, 0);
2014b4380da7SHans Petter Selasky 		if (len == 0)
2015b4380da7SHans Petter Selasky 			break;
2016b4380da7SHans Petter Selasky 		if (len > sizeof(buf))
2017b4380da7SHans Petter Selasky 			len = sizeof(buf);
2018b4380da7SHans Petter Selasky 
2019b4380da7SHans Petter Selasky 		memset(buf, 0, sizeof(buf));
2020b4380da7SHans Petter Selasky 
2021b4380da7SHans Petter Selasky 		pc = usbd_xfer_get_frame(xfer, 0);
2022b4380da7SHans Petter Selasky 		usbd_copy_out(pc, 0, buf, len);
2023b4380da7SHans Petter Selasky 
2024b4380da7SHans Petter Selasky 		temp = UGETDW(buf);
2025b4380da7SHans Petter Selasky 
2026b4380da7SHans Petter Selasky 		DPRINTF("Value = 0x%08x\n", (int)temp);
2027b4380da7SHans Petter Selasky 
2028b4380da7SHans Petter Selasky 		/* auto-detect SYNC format */
2029b4380da7SHans Petter Selasky 
2030b4380da7SHans Petter Selasky 		if (len == 4)
2031b4380da7SHans Petter Selasky 			temp &= 0x0fffffff;
2032b4380da7SHans Petter Selasky 
2033b4380da7SHans Petter Selasky 		/* check for no data */
2034b4380da7SHans Petter Selasky 
2035b4380da7SHans Petter Selasky 		if (temp == 0)
2036b4380da7SHans Petter Selasky 			break;
2037b4380da7SHans Petter Selasky 
2038a931ce67SHans Petter Selasky 		temp *= 125ULL;
2039b4380da7SHans Petter Selasky 
204085bad582SHans Petter Selasky 		sample_rate = ch->usb_alt[ch->cur_alt].sample_rate;
204185bad582SHans Petter Selasky 
2042b4380da7SHans Petter Selasky 		/* auto adjust */
2043455a367fSHans Petter Selasky 		while (temp < (sample_rate - (sample_rate / 4)))
2044b4380da7SHans Petter Selasky 			temp *= 2;
2045b4380da7SHans Petter Selasky 
2046455a367fSHans Petter Selasky 		while (temp > (sample_rate + (sample_rate / 2)))
2047b4380da7SHans Petter Selasky 			temp /= 2;
2048b4380da7SHans Petter Selasky 
204985bad582SHans Petter Selasky 		DPRINTF("Comparing %d Hz :: %d Hz\n",
205085bad582SHans Petter Selasky 		    (int)temp, (int)sample_rate);
2051b4380da7SHans Petter Selasky 
205287087b85SHans Petter Selasky 		/*
205387087b85SHans Petter Selasky 		 * Use feedback value as fallback when there is no
205487087b85SHans Petter Selasky 		 * recording channel:
205587087b85SHans Petter Selasky 		 */
205687087b85SHans Petter Selasky 		if (ch->priv_sc->sc_rec_chan.num_alt == 0)
205787087b85SHans Petter Selasky 			ch->jitter_curr = temp - sample_rate;
205887087b85SHans Petter Selasky 
205985bad582SHans Petter Selasky 		ch->feedback_rate = temp;
2060b4380da7SHans Petter Selasky 		break;
2061b4380da7SHans Petter Selasky 
2062b4380da7SHans Petter Selasky 	case USB_ST_SETUP:
2063b4380da7SHans Petter Selasky 		usbd_xfer_set_frames(xfer, 1);
2064b4380da7SHans Petter Selasky 		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_framelen(xfer));
2065b4380da7SHans Petter Selasky 		usbd_transfer_submit(xfer);
2066b4380da7SHans Petter Selasky 		break;
2067b4380da7SHans Petter Selasky 
2068b4380da7SHans Petter Selasky 	default:			/* Error */
2069b4380da7SHans Petter Selasky 		break;
2070b4380da7SHans Petter Selasky 	}
2071b4380da7SHans Petter Selasky }
2072b4380da7SHans Petter Selasky 
207385bad582SHans Petter Selasky static int
207485bad582SHans Petter Selasky uaudio_chan_is_async(struct uaudio_chan *ch, uint8_t alt)
207585bad582SHans Petter Selasky {
207685bad582SHans Petter Selasky 	uint8_t attr = ch->usb_alt[alt].p_ed1->bmAttributes;
207785bad582SHans Petter Selasky 	return (UE_GET_ISO_TYPE(attr) == UE_ISO_ASYNC);
207885bad582SHans Petter Selasky }
207985bad582SHans Petter Selasky 
2080b4380da7SHans Petter Selasky static void
2081ed6d949aSAndrew Thompson uaudio_chan_play_callback(struct usb_xfer *xfer, usb_error_t error)
20823a3f90c6SAndrew Thompson {
2083ed6d949aSAndrew Thompson 	struct uaudio_chan *ch = usbd_xfer_softc(xfer);
208485bad582SHans Petter Selasky 	struct uaudio_chan *ch_rec;
2085ed6d949aSAndrew Thompson 	struct usb_page_cache *pc;
2086b4380da7SHans Petter Selasky 	uint32_t mfl;
20873a3f90c6SAndrew Thompson 	uint32_t total;
20883a3f90c6SAndrew Thompson 	uint32_t blockcount;
20893a3f90c6SAndrew Thompson 	uint32_t n;
20903a3f90c6SAndrew Thompson 	uint32_t offset;
209185bad582SHans Petter Selasky 	int sample_size;
2092afbfddd9SAndrew Thompson 	int actlen;
2093afbfddd9SAndrew Thompson 	int sumlen;
2094ed6d949aSAndrew Thompson 
209585bad582SHans Petter Selasky 	if (ch->running == 0 || ch->start == ch->end) {
209685bad582SHans Petter Selasky 		DPRINTF("not running or no buffer!\n");
2097b029f6bbSAndrew Thompson 		return;
2098b029f6bbSAndrew Thompson 	}
20993a3f90c6SAndrew Thompson 
210085bad582SHans Petter Selasky 	/* check if there is a record channel */
210185bad582SHans Petter Selasky 	if (ch->priv_sc->sc_rec_chan.num_alt > 0)
210285bad582SHans Petter Selasky 		ch_rec = &ch->priv_sc->sc_rec_chan;
210385bad582SHans Petter Selasky 	else
210485bad582SHans Petter Selasky 		ch_rec = NULL;
210585bad582SHans Petter Selasky 
210685bad582SHans Petter Selasky 	usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
210785bad582SHans Petter Selasky 
21083a3f90c6SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
210985bad582SHans Petter Selasky 	case USB_ST_SETUP:
211085bad582SHans Petter Selasky tr_setup:
211185bad582SHans Petter Selasky 		if (ch_rec != NULL) {
211285bad582SHans Petter Selasky 			/* reset receive jitter counters */
211385bad582SHans Petter Selasky 			mtx_lock(ch_rec->pcm_mtx);
211485bad582SHans Petter Selasky 			ch_rec->jitter_curr = 0;
211585bad582SHans Petter Selasky 			ch_rec->jitter_rem = 0;
211685bad582SHans Petter Selasky 			mtx_unlock(ch_rec->pcm_mtx);
211785bad582SHans Petter Selasky 		}
211885bad582SHans Petter Selasky 
211985bad582SHans Petter Selasky 		/* reset transmit jitter counters */
212085bad582SHans Petter Selasky 		ch->jitter_curr = 0;
212185bad582SHans Petter Selasky 		ch->jitter_rem = 0;
212285bad582SHans Petter Selasky 
212385bad582SHans Petter Selasky 		/* FALLTHROUGH */
21243a3f90c6SAndrew Thompson 	case USB_ST_TRANSFERRED:
2125ed6d949aSAndrew Thompson 		if (actlen < sumlen) {
21263a3f90c6SAndrew Thompson 			DPRINTF("short transfer, "
2127afbfddd9SAndrew Thompson 			    "%d of %d bytes\n", actlen, sumlen);
21283a3f90c6SAndrew Thompson 		}
21293a3f90c6SAndrew Thompson 		chn_intr(ch->pcm_ch);
21303a3f90c6SAndrew Thompson 
213185bad582SHans Petter Selasky 		/*
213285bad582SHans Petter Selasky 		 * Check for asynchronous playback endpoint and that
213385bad582SHans Petter Selasky 		 * the playback endpoint is properly configured:
213485bad582SHans Petter Selasky 		 */
213585bad582SHans Petter Selasky 		if (ch_rec != NULL &&
213685bad582SHans Petter Selasky 		    uaudio_chan_is_async(ch, ch->cur_alt) != 0) {
213785bad582SHans Petter Selasky 			mtx_lock(ch_rec->pcm_mtx);
213885bad582SHans Petter Selasky 			if (ch_rec->cur_alt < ch_rec->num_alt) {
213985bad582SHans Petter Selasky 				int64_t tx_jitter;
214085bad582SHans Petter Selasky 				int64_t rx_rate;
214185bad582SHans Petter Selasky 
214285bad582SHans Petter Selasky 				/* translate receive jitter into transmit jitter */
214385bad582SHans Petter Selasky 				tx_jitter = ch->usb_alt[ch->cur_alt].sample_rate;
214485bad582SHans Petter Selasky 				tx_jitter = (tx_jitter * ch_rec->jitter_curr) +
214585bad582SHans Petter Selasky 				    ch->jitter_rem;
214685bad582SHans Petter Selasky 
214785bad582SHans Petter Selasky 				/* reset receive jitter counters */
214885bad582SHans Petter Selasky 				ch_rec->jitter_curr = 0;
214985bad582SHans Petter Selasky 				ch_rec->jitter_rem = 0;
215085bad582SHans Petter Selasky 
215185bad582SHans Petter Selasky 				/* compute exact number of transmit jitter samples */
215285bad582SHans Petter Selasky 				rx_rate = ch_rec->usb_alt[ch_rec->cur_alt].sample_rate;
215385bad582SHans Petter Selasky 				ch->jitter_curr += tx_jitter / rx_rate;
215485bad582SHans Petter Selasky 				ch->jitter_rem = tx_jitter % rx_rate;
215585bad582SHans Petter Selasky 			}
215685bad582SHans Petter Selasky 			mtx_unlock(ch_rec->pcm_mtx);
215785bad582SHans Petter Selasky 		}
215885bad582SHans Petter Selasky 
2159a931ce67SHans Petter Selasky 		/* start the SYNC transfer one time per second, if any */
216085bad582SHans Petter Selasky 		if (++(ch->intr_counter) >= UAUDIO_IRQS) {
216185bad582SHans Petter Selasky 			ch->intr_counter = 0;
2162b4380da7SHans Petter Selasky 			usbd_transfer_start(ch->xfer[UAUDIO_NCHANBUFS]);
216326ec3491SHans Petter Selasky 		}
2164b4380da7SHans Petter Selasky 
2165b4380da7SHans Petter Selasky 		mfl = usbd_xfer_max_framelen(xfer);
2166b4380da7SHans Petter Selasky 
2167b4380da7SHans Petter Selasky 		if (ch->bytes_per_frame[1] > mfl) {
21683a3f90c6SAndrew Thompson 			DPRINTF("bytes per transfer, %d, "
21693a3f90c6SAndrew Thompson 			    "exceeds maximum, %d!\n",
2170afbfddd9SAndrew Thompson 			    ch->bytes_per_frame[1],
2171b4380da7SHans Petter Selasky 			    mfl);
21723a3f90c6SAndrew Thompson 			break;
21733a3f90c6SAndrew Thompson 		}
2174afbfddd9SAndrew Thompson 
2175afbfddd9SAndrew Thompson 		blockcount = ch->intr_frames;
2176afbfddd9SAndrew Thompson 
2177afbfddd9SAndrew Thompson 		/* setup number of frames */
2178ed6d949aSAndrew Thompson 		usbd_xfer_set_frames(xfer, blockcount);
2179afbfddd9SAndrew Thompson 
218085bad582SHans Petter Selasky 		/* get sample size */
218185bad582SHans Petter Selasky 		sample_size = ch->usb_alt[ch->cur_alt].sample_size;
218285bad582SHans Petter Selasky 
2183afbfddd9SAndrew Thompson 		/* reset total length */
2184afbfddd9SAndrew Thompson 		total = 0;
2185afbfddd9SAndrew Thompson 
2186afbfddd9SAndrew Thompson 		/* setup frame lengths */
2187afbfddd9SAndrew Thompson 		for (n = 0; n != blockcount; n++) {
2188b4380da7SHans Petter Selasky 			uint32_t frame_len;
2189b4380da7SHans Petter Selasky 
2190afbfddd9SAndrew Thompson 			ch->sample_curr += ch->sample_rem;
2191afbfddd9SAndrew Thompson 			if (ch->sample_curr >= ch->frames_per_second) {
2192afbfddd9SAndrew Thompson 				ch->sample_curr -= ch->frames_per_second;
2193b4380da7SHans Petter Selasky 				frame_len = ch->bytes_per_frame[1];
2194afbfddd9SAndrew Thompson 			} else {
2195b4380da7SHans Petter Selasky 				frame_len = ch->bytes_per_frame[0];
2196afbfddd9SAndrew Thompson 			}
2197b4380da7SHans Petter Selasky 
219885bad582SHans Petter Selasky 			/* handle free running clock case */
219985bad582SHans Petter Selasky 			if (ch->jitter_curr > 0 &&
220085bad582SHans Petter Selasky 			    (frame_len + sample_size) <= mfl) {
2201b4380da7SHans Petter Selasky 				DPRINTFN(6, "sending one sample more\n");
220285bad582SHans Petter Selasky 				ch->jitter_curr--;
2203455a367fSHans Petter Selasky 				frame_len += sample_size;
220485bad582SHans Petter Selasky 			} else if (ch->jitter_curr < 0 &&
220585bad582SHans Petter Selasky 			    frame_len >= sample_size) {
2206b4380da7SHans Petter Selasky 				DPRINTFN(6, "sending one sample less\n");
220785bad582SHans Petter Selasky 				ch->jitter_curr++;
2208455a367fSHans Petter Selasky 				frame_len -= sample_size;
2209b4380da7SHans Petter Selasky 			}
2210b4380da7SHans Petter Selasky 			usbd_xfer_set_frame_len(xfer, n, frame_len);
2211b4380da7SHans Petter Selasky 			total += frame_len;
2212afbfddd9SAndrew Thompson 		}
22133a3f90c6SAndrew Thompson 
221485bad582SHans Petter Selasky 		DPRINTFN(6, "transferring %d bytes\n", total);
22153a3f90c6SAndrew Thompson 
22163a3f90c6SAndrew Thompson 		offset = 0;
22173a3f90c6SAndrew Thompson 
2218ed6d949aSAndrew Thompson 		pc = usbd_xfer_get_frame(xfer, 0);
22193a3f90c6SAndrew Thompson 		while (total > 0) {
22203a3f90c6SAndrew Thompson 
22213a3f90c6SAndrew Thompson 			n = (ch->end - ch->cur);
222285bad582SHans Petter Selasky 			if (n > total)
22233a3f90c6SAndrew Thompson 				n = total;
222485bad582SHans Petter Selasky 
2225ed6d949aSAndrew Thompson 			usbd_copy_in(pc, offset, ch->cur, n);
22263a3f90c6SAndrew Thompson 
22273a3f90c6SAndrew Thompson 			total -= n;
22283a3f90c6SAndrew Thompson 			ch->cur += n;
22293a3f90c6SAndrew Thompson 			offset += n;
22303a3f90c6SAndrew Thompson 
223185bad582SHans Petter Selasky 			if (ch->cur >= ch->end)
22323a3f90c6SAndrew Thompson 				ch->cur = ch->start;
22333a3f90c6SAndrew Thompson 		}
2234a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
22353a3f90c6SAndrew Thompson 		break;
22363a3f90c6SAndrew Thompson 
22373a3f90c6SAndrew Thompson 	default:			/* Error */
223885bad582SHans Petter Selasky 		if (error != USB_ERR_CANCELLED)
223985bad582SHans Petter Selasky 			goto tr_setup;
22403a3f90c6SAndrew Thompson 		break;
22413a3f90c6SAndrew Thompson 	}
22423a3f90c6SAndrew Thompson }
22433a3f90c6SAndrew Thompson 
22443a3f90c6SAndrew Thompson static void
2245b4380da7SHans Petter Selasky uaudio_chan_record_sync_callback(struct usb_xfer *xfer, usb_error_t error)
2246b4380da7SHans Petter Selasky {
2247b4380da7SHans Petter Selasky 	/* TODO */
2248b4380da7SHans Petter Selasky }
2249b4380da7SHans Petter Selasky 
2250b4380da7SHans Petter Selasky static void
2251ed6d949aSAndrew Thompson uaudio_chan_record_callback(struct usb_xfer *xfer, usb_error_t error)
22523a3f90c6SAndrew Thompson {
2253ed6d949aSAndrew Thompson 	struct uaudio_chan *ch = usbd_xfer_softc(xfer);
2254ed6d949aSAndrew Thompson 	struct usb_page_cache *pc;
22553a3f90c6SAndrew Thompson 	uint32_t offset0;
2256b029f6bbSAndrew Thompson 	uint32_t mfl;
22576d917491SHans Petter Selasky 	int m;
22586d917491SHans Petter Selasky 	int n;
2259ed6d949aSAndrew Thompson 	int len;
2260b029f6bbSAndrew Thompson 	int actlen;
2261b029f6bbSAndrew Thompson 	int nframes;
226285bad582SHans Petter Selasky 	int expected_bytes;
226385bad582SHans Petter Selasky 	int sample_size;
2264ed6d949aSAndrew Thompson 
226585bad582SHans Petter Selasky 	if (ch->start == ch->end) {
2266b029f6bbSAndrew Thompson 		DPRINTF("no buffer!\n");
2267b029f6bbSAndrew Thompson 		return;
2268b029f6bbSAndrew Thompson 	}
22693a3f90c6SAndrew Thompson 
227085bad582SHans Petter Selasky 	usbd_xfer_status(xfer, &actlen, NULL, NULL, &nframes);
227185bad582SHans Petter Selasky 	mfl = usbd_xfer_max_framelen(xfer);
227285bad582SHans Petter Selasky 
22733a3f90c6SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
22743a3f90c6SAndrew Thompson 	case USB_ST_TRANSFERRED:
2275afbfddd9SAndrew Thompson 
22763a3f90c6SAndrew Thompson 		offset0 = 0;
2277b029f6bbSAndrew Thompson 		pc = usbd_xfer_get_frame(xfer, 0);
22783a3f90c6SAndrew Thompson 
227985bad582SHans Petter Selasky 		/* try to compute the number of expected bytes */
228085bad582SHans Petter Selasky 		ch->sample_curr += (ch->sample_rem * ch->intr_frames);
22813a3f90c6SAndrew Thompson 
228285bad582SHans Petter Selasky 		/* compute number of expected bytes */
228385bad582SHans Petter Selasky 		expected_bytes = (ch->intr_frames * ch->bytes_per_frame[0]) +
228485bad582SHans Petter Selasky 		    ((ch->sample_curr / ch->frames_per_second) *
228585bad582SHans Petter Selasky 		    (ch->bytes_per_frame[1] - ch->bytes_per_frame[0]));
228685bad582SHans Petter Selasky 
228785bad582SHans Petter Selasky 		/* keep remainder */
228885bad582SHans Petter Selasky 		ch->sample_curr %= ch->frames_per_second;
228985bad582SHans Petter Selasky 
229085bad582SHans Petter Selasky 		/* get current sample size */
229185bad582SHans Petter Selasky 		sample_size = ch->usb_alt[ch->cur_alt].sample_size;
229285bad582SHans Petter Selasky 
229385bad582SHans Petter Selasky 		for (n = 0; n != nframes; n++) {
229485bad582SHans Petter Selasky 			uint32_t offset1 = offset0;
229585bad582SHans Petter Selasky 
22968f9e0ef9SAndrew Thompson 			len = usbd_xfer_frame_len(xfer, n);
22973a3f90c6SAndrew Thompson 
229885bad582SHans Petter Selasky 			/* make sure we only receive complete samples */
229985bad582SHans Petter Selasky 			len = len - (len % sample_size);
230085bad582SHans Petter Selasky 
230185bad582SHans Petter Selasky 			/* subtract bytes received from expected payload */
230285bad582SHans Petter Selasky 			expected_bytes -= len;
230385bad582SHans Petter Selasky 
230485bad582SHans Petter Selasky 			/* don't receive data when not ready */
230585bad582SHans Petter Selasky 			if (ch->running == 0 || ch->cur_alt != ch->set_alt)
230685bad582SHans Petter Selasky 				continue;
230785bad582SHans Petter Selasky 
230885bad582SHans Petter Selasky 			/* fill ring buffer with samples, if any */
2309ed6d949aSAndrew Thompson 			while (len > 0) {
23103a3f90c6SAndrew Thompson 
23113a3f90c6SAndrew Thompson 				m = (ch->end - ch->cur);
23123a3f90c6SAndrew Thompson 
23136d917491SHans Petter Selasky 				if (m > len)
2314ed6d949aSAndrew Thompson 					m = len;
23156d917491SHans Petter Selasky 
2316ed6d949aSAndrew Thompson 				usbd_copy_out(pc, offset1, ch->cur, m);
23173a3f90c6SAndrew Thompson 
2318ed6d949aSAndrew Thompson 				len -= m;
23193a3f90c6SAndrew Thompson 				offset1 += m;
23203a3f90c6SAndrew Thompson 				ch->cur += m;
23213a3f90c6SAndrew Thompson 
232285bad582SHans Petter Selasky 				if (ch->cur >= ch->end)
23233a3f90c6SAndrew Thompson 					ch->cur = ch->start;
23243a3f90c6SAndrew Thompson 			}
23253a3f90c6SAndrew Thompson 
2326b029f6bbSAndrew Thompson 			offset0 += mfl;
23273a3f90c6SAndrew Thompson 		}
23283a3f90c6SAndrew Thompson 
232985bad582SHans Petter Selasky 		/* update current jitter */
233085bad582SHans Petter Selasky 		ch->jitter_curr -= (expected_bytes / sample_size);
233185bad582SHans Petter Selasky 
233285bad582SHans Petter Selasky 		/* don't allow a huge amount of jitter to accumulate */
233385bad582SHans Petter Selasky 		nframes = 2 * ch->intr_frames;
233485bad582SHans Petter Selasky 
233585bad582SHans Petter Selasky 		/* range check current jitter */
233685bad582SHans Petter Selasky 		if (ch->jitter_curr < -nframes)
233785bad582SHans Petter Selasky 			ch->jitter_curr = -nframes;
233885bad582SHans Petter Selasky 		else if (ch->jitter_curr > nframes)
233985bad582SHans Petter Selasky 			ch->jitter_curr = nframes;
234085bad582SHans Petter Selasky 
234185bad582SHans Petter Selasky 		DPRINTFN(6, "transferred %d bytes, jitter %d samples\n",
234285bad582SHans Petter Selasky 		    actlen, ch->jitter_curr);
234385bad582SHans Petter Selasky 
234485bad582SHans Petter Selasky 		if (ch->running != 0)
23453a3f90c6SAndrew Thompson 			chn_intr(ch->pcm_ch);
23463a3f90c6SAndrew Thompson 
23473a3f90c6SAndrew Thompson 	case USB_ST_SETUP:
2348b029f6bbSAndrew Thompson tr_setup:
234985bad582SHans Petter Selasky 		nframes = ch->intr_frames;
2350afbfddd9SAndrew Thompson 
235185bad582SHans Petter Selasky 		usbd_xfer_set_frames(xfer, nframes);
235285bad582SHans Petter Selasky 		for (n = 0; n != nframes; n++)
2353b029f6bbSAndrew Thompson 			usbd_xfer_set_frame_len(xfer, n, mfl);
23543a3f90c6SAndrew Thompson 
2355a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
2356b029f6bbSAndrew Thompson 		break;
23573a3f90c6SAndrew Thompson 
23583a3f90c6SAndrew Thompson 	default:			/* Error */
235985bad582SHans Petter Selasky 		if (error != USB_ERR_CANCELLED)
2360b029f6bbSAndrew Thompson 			goto tr_setup;
236185bad582SHans Petter Selasky 		break;
23623a3f90c6SAndrew Thompson 	}
23633a3f90c6SAndrew Thompson }
23643a3f90c6SAndrew Thompson 
23653a3f90c6SAndrew Thompson void   *
23663a3f90c6SAndrew Thompson uaudio_chan_init(struct uaudio_softc *sc, struct snd_dbuf *b,
23673a3f90c6SAndrew Thompson     struct pcm_channel *c, int dir)
23683a3f90c6SAndrew Thompson {
23693a3f90c6SAndrew Thompson 	struct uaudio_chan *ch = ((dir == PCMDIR_PLAY) ?
23703a3f90c6SAndrew Thompson 	    &sc->sc_play_chan : &sc->sc_rec_chan);
23713a3f90c6SAndrew Thompson 	uint32_t buf_size;
2372455a367fSHans Petter Selasky 	uint8_t x;
23733a3f90c6SAndrew Thompson 
2374455a367fSHans Petter Selasky 	/* store mutex and PCM channel */
23757fb43570SAndrew Thompson 
23767fb43570SAndrew Thompson 	ch->pcm_ch = c;
23777fb43570SAndrew Thompson 	ch->pcm_mtx = c->lock;
23787fb43570SAndrew Thompson 
2379455a367fSHans Petter Selasky 	/* compute worst case buffer */
23803a3f90c6SAndrew Thompson 
2381455a367fSHans Petter Selasky 	buf_size = 0;
2382455a367fSHans Petter Selasky 	for (x = 0; x != ch->num_alt; x++) {
2383455a367fSHans Petter Selasky 		uint32_t temp = uaudio_get_buffer_size(ch, x);
2384455a367fSHans Petter Selasky 		if (temp > buf_size)
2385455a367fSHans Petter Selasky 			buf_size = temp;
2386afbfddd9SAndrew Thompson 	}
2387afbfddd9SAndrew Thompson 
2388455a367fSHans Petter Selasky 	/* allow double buffering */
2389b029f6bbSAndrew Thompson 	buf_size *= 2;
2390455a367fSHans Petter Selasky 
2391455a367fSHans Petter Selasky 	DPRINTF("Worst case buffer is %d bytes\n", (int)buf_size);
2392b029f6bbSAndrew Thompson 
2393b029f6bbSAndrew Thompson 	ch->buf = malloc(buf_size, M_DEVBUF, M_WAITOK | M_ZERO);
2394b029f6bbSAndrew Thompson 	if (ch->buf == NULL)
2395b029f6bbSAndrew Thompson 		goto error;
2396b029f6bbSAndrew Thompson 	if (sndbuf_setup(b, ch->buf, buf_size) != 0)
2397b029f6bbSAndrew Thompson 		goto error;
2398b029f6bbSAndrew Thompson 
2399b029f6bbSAndrew Thompson 	ch->start = ch->buf;
2400b029f6bbSAndrew Thompson 	ch->end = ch->buf + buf_size;
2401b029f6bbSAndrew Thompson 	ch->cur = ch->buf;
2402b029f6bbSAndrew Thompson 	ch->pcm_buf = b;
2403455a367fSHans Petter Selasky 	ch->max_buf = buf_size;
2404b029f6bbSAndrew Thompson 
2405b029f6bbSAndrew Thompson 	if (ch->pcm_mtx == NULL) {
2406b029f6bbSAndrew Thompson 		DPRINTF("ERROR: PCM channels does not have a mutex!\n");
2407b029f6bbSAndrew Thompson 		goto error;
2408b029f6bbSAndrew Thompson 	}
24093a3f90c6SAndrew Thompson 	return (ch);
24103a3f90c6SAndrew Thompson 
24113a3f90c6SAndrew Thompson error:
24123a3f90c6SAndrew Thompson 	uaudio_chan_free(ch);
24133a3f90c6SAndrew Thompson 	return (NULL);
24143a3f90c6SAndrew Thompson }
24153a3f90c6SAndrew Thompson 
24163a3f90c6SAndrew Thompson int
24173a3f90c6SAndrew Thompson uaudio_chan_free(struct uaudio_chan *ch)
24183a3f90c6SAndrew Thompson {
24193a3f90c6SAndrew Thompson 	if (ch->buf != NULL) {
24203a3f90c6SAndrew Thompson 		free(ch->buf, M_DEVBUF);
24213a3f90c6SAndrew Thompson 		ch->buf = NULL;
24223a3f90c6SAndrew Thompson 	}
2423b4380da7SHans Petter Selasky 	usbd_transfer_unsetup(ch->xfer, UAUDIO_NCHANBUFS + 1);
24243a3f90c6SAndrew Thompson 
2425455a367fSHans Petter Selasky 	ch->num_alt = 0;
24263a3f90c6SAndrew Thompson 
24273a3f90c6SAndrew Thompson 	return (0);
24283a3f90c6SAndrew Thompson }
24293a3f90c6SAndrew Thompson 
24303a3f90c6SAndrew Thompson int
24313a3f90c6SAndrew Thompson uaudio_chan_set_param_blocksize(struct uaudio_chan *ch, uint32_t blocksize)
24323a3f90c6SAndrew Thompson {
2433455a367fSHans Petter Selasky 	uint32_t temp = 2 * uaudio_get_buffer_size(ch, ch->set_alt);
2434455a367fSHans Petter Selasky 	sndbuf_setup(ch->pcm_buf, ch->buf, temp);
2435455a367fSHans Petter Selasky 	return (temp / 2);
24363a3f90c6SAndrew Thompson }
24373a3f90c6SAndrew Thompson 
24383a3f90c6SAndrew Thompson int
24393a3f90c6SAndrew Thompson uaudio_chan_set_param_fragments(struct uaudio_chan *ch, uint32_t blocksize,
24403a3f90c6SAndrew Thompson     uint32_t blockcount)
24413a3f90c6SAndrew Thompson {
24423a3f90c6SAndrew Thompson 	return (1);
24433a3f90c6SAndrew Thompson }
24443a3f90c6SAndrew Thompson 
24453a3f90c6SAndrew Thompson int
24463a3f90c6SAndrew Thompson uaudio_chan_set_param_speed(struct uaudio_chan *ch, uint32_t speed)
24473a3f90c6SAndrew Thompson {
244885bad582SHans Petter Selasky 	struct uaudio_softc *sc;
2449455a367fSHans Petter Selasky 	uint8_t x;
2450455a367fSHans Petter Selasky 
245185bad582SHans Petter Selasky 	sc = ch->priv_sc;
245285bad582SHans Petter Selasky 
2453455a367fSHans Petter Selasky 	for (x = 0; x < ch->num_alt; x++) {
2454455a367fSHans Petter Selasky 		if (ch->usb_alt[x].sample_rate < speed) {
2455455a367fSHans Petter Selasky 			/* sample rate is too low */
2456455a367fSHans Petter Selasky 			break;
24573a3f90c6SAndrew Thompson 		}
2458455a367fSHans Petter Selasky 	}
2459455a367fSHans Petter Selasky 
2460455a367fSHans Petter Selasky 	if (x != 0)
2461455a367fSHans Petter Selasky 		x--;
2462455a367fSHans Petter Selasky 
246385bad582SHans Petter Selasky 	usb_proc_explore_lock(sc->sc_udev);
2464455a367fSHans Petter Selasky 	ch->set_alt = x;
246585bad582SHans Petter Selasky 	usb_proc_explore_unlock(sc->sc_udev);
2466455a367fSHans Petter Selasky 
2467455a367fSHans Petter Selasky 	DPRINTF("Selecting alt %d\n", (int)x);
2468455a367fSHans Petter Selasky 
2469455a367fSHans Petter Selasky 	return (ch->usb_alt[x].sample_rate);
24703a3f90c6SAndrew Thompson }
24713a3f90c6SAndrew Thompson 
24723a3f90c6SAndrew Thompson int
24733a3f90c6SAndrew Thompson uaudio_chan_getptr(struct uaudio_chan *ch)
24743a3f90c6SAndrew Thompson {
24753a3f90c6SAndrew Thompson 	return (ch->cur - ch->start);
24763a3f90c6SAndrew Thompson }
24773a3f90c6SAndrew Thompson 
24783a3f90c6SAndrew Thompson struct pcmchan_caps *
24793a3f90c6SAndrew Thompson uaudio_chan_getcaps(struct uaudio_chan *ch)
24803a3f90c6SAndrew Thompson {
24813a3f90c6SAndrew Thompson 	return (&ch->pcm_cap);
24823a3f90c6SAndrew Thompson }
24833a3f90c6SAndrew Thompson 
248490da2b28SAriff Abdullah static struct pcmchan_matrix uaudio_chan_matrix_swap_2_0 = {
248590da2b28SAriff Abdullah 	.id = SND_CHN_MATRIX_DRV,
248690da2b28SAriff Abdullah 	.channels = 2,
248790da2b28SAriff Abdullah 	.ext = 0,
248890da2b28SAriff Abdullah 	.map = {
248990da2b28SAriff Abdullah 		/* Right */
249090da2b28SAriff Abdullah 		[0] = {
249190da2b28SAriff Abdullah 			.type = SND_CHN_T_FR,
249290da2b28SAriff Abdullah 			.members =
249390da2b28SAriff Abdullah 			    SND_CHN_T_MASK_FR | SND_CHN_T_MASK_FC |
249490da2b28SAriff Abdullah 			    SND_CHN_T_MASK_LF | SND_CHN_T_MASK_BR |
249590da2b28SAriff Abdullah 			    SND_CHN_T_MASK_BC | SND_CHN_T_MASK_SR
249690da2b28SAriff Abdullah 		},
249790da2b28SAriff Abdullah 		/* Left */
249890da2b28SAriff Abdullah 		[1] = {
249990da2b28SAriff Abdullah 			.type = SND_CHN_T_FL,
250090da2b28SAriff Abdullah 			.members =
250190da2b28SAriff Abdullah 			    SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FC |
250290da2b28SAriff Abdullah 			    SND_CHN_T_MASK_LF | SND_CHN_T_MASK_BL |
250390da2b28SAriff Abdullah 			    SND_CHN_T_MASK_BC | SND_CHN_T_MASK_SL
250490da2b28SAriff Abdullah 		},
250590da2b28SAriff Abdullah 		[2] = {
250690da2b28SAriff Abdullah 			.type = SND_CHN_T_MAX,
250790da2b28SAriff Abdullah 			.members = 0
250890da2b28SAriff Abdullah 		}
250990da2b28SAriff Abdullah 	},
251090da2b28SAriff Abdullah 	.mask = SND_CHN_T_MASK_FR | SND_CHN_T_MASK_FL,
251190da2b28SAriff Abdullah 	.offset = {  1,  0, -1, -1, -1, -1, -1, -1, -1,
251290da2b28SAriff Abdullah 		    -1, -1, -1, -1, -1, -1, -1, -1, -1  }
251390da2b28SAriff Abdullah };
251490da2b28SAriff Abdullah 
251590da2b28SAriff Abdullah struct pcmchan_matrix *
251690da2b28SAriff Abdullah uaudio_chan_getmatrix(struct uaudio_chan *ch, uint32_t format)
251790da2b28SAriff Abdullah {
251890da2b28SAriff Abdullah 	struct uaudio_softc *sc;
251990da2b28SAriff Abdullah 
252090da2b28SAriff Abdullah 	sc = ch->priv_sc;
252190da2b28SAriff Abdullah 
252290da2b28SAriff Abdullah 	if (sc != NULL && sc->sc_uq_audio_swap_lr != 0 &&
252390da2b28SAriff Abdullah 	    AFMT_CHANNEL(format) == 2)
252490da2b28SAriff Abdullah 		return (&uaudio_chan_matrix_swap_2_0);
252590da2b28SAriff Abdullah 
252690da2b28SAriff Abdullah 	return (feeder_matrix_format_map(format));
252790da2b28SAriff Abdullah }
252890da2b28SAriff Abdullah 
25293a3f90c6SAndrew Thompson int
25303a3f90c6SAndrew Thompson uaudio_chan_set_param_format(struct uaudio_chan *ch, uint32_t format)
25313a3f90c6SAndrew Thompson {
2532455a367fSHans Petter Selasky 	DPRINTF("Selecting format 0x%08x\n", (unsigned int)format);
25333a3f90c6SAndrew Thompson 	return (0);
25343a3f90c6SAndrew Thompson }
25353a3f90c6SAndrew Thompson 
253685bad582SHans Petter Selasky static void
253785bad582SHans Petter Selasky uaudio_chan_start_sub(struct uaudio_chan *ch)
25383a3f90c6SAndrew Thompson {
2539455a367fSHans Petter Selasky 	struct uaudio_softc *sc = ch->priv_sc;
2540455a367fSHans Petter Selasky 	int do_start = 0;
25413a3f90c6SAndrew Thompson 
2542455a367fSHans Petter Selasky 	if (ch->operation != CHAN_OP_DRAIN) {
2543455a367fSHans Petter Selasky 		if (ch->cur_alt == ch->set_alt &&
254485bad582SHans Petter Selasky 		    ch->operation == CHAN_OP_NONE &&
254585bad582SHans Petter Selasky 		    mtx_owned(ch->pcm_mtx) != 0) {
2546455a367fSHans Petter Selasky 			/* save doing the explore task */
2547455a367fSHans Petter Selasky 			do_start = 1;
2548455a367fSHans Petter Selasky 		} else {
2549455a367fSHans Petter Selasky 			ch->operation = CHAN_OP_START;
2550455a367fSHans Petter Selasky 			(void)usb_proc_explore_msignal(sc->sc_udev,
2551455a367fSHans Petter Selasky 			    &sc->sc_config_msg[0], &sc->sc_config_msg[1]);
2552455a367fSHans Petter Selasky 		}
2553455a367fSHans Petter Selasky 	}
2554455a367fSHans Petter Selasky 	if (do_start) {
2555a593f6b8SAndrew Thompson 		usbd_transfer_start(ch->xfer[0]);
2556a593f6b8SAndrew Thompson 		usbd_transfer_start(ch->xfer[1]);
2557455a367fSHans Petter Selasky 	}
25583a3f90c6SAndrew Thompson }
25593a3f90c6SAndrew Thompson 
256085bad582SHans Petter Selasky static int
256185bad582SHans Petter Selasky uaudio_chan_need_both(struct uaudio_softc *sc)
256285bad582SHans Petter Selasky {
256385bad582SHans Petter Selasky 	return (sc->sc_play_chan.num_alt > 0 &&
256485bad582SHans Petter Selasky 	    sc->sc_play_chan.running != 0 &&
256585bad582SHans Petter Selasky 	    uaudio_chan_is_async(&sc->sc_play_chan,
256685bad582SHans Petter Selasky 	    sc->sc_play_chan.set_alt) != 0 &&
256785bad582SHans Petter Selasky 	    sc->sc_rec_chan.num_alt > 0 &&
256885bad582SHans Petter Selasky 	    sc->sc_rec_chan.running == 0);
256985bad582SHans Petter Selasky }
257085bad582SHans Petter Selasky 
257185bad582SHans Petter Selasky static int
257285bad582SHans Petter Selasky uaudio_chan_need_none(struct uaudio_softc *sc)
257385bad582SHans Petter Selasky {
257485bad582SHans Petter Selasky 	return (sc->sc_play_chan.num_alt > 0 &&
257585bad582SHans Petter Selasky 	    sc->sc_play_chan.running == 0 &&
257685bad582SHans Petter Selasky 	    sc->sc_rec_chan.num_alt > 0 &&
257785bad582SHans Petter Selasky 	    sc->sc_rec_chan.running == 0);
257885bad582SHans Petter Selasky }
257985bad582SHans Petter Selasky 
258085bad582SHans Petter Selasky void
258185bad582SHans Petter Selasky uaudio_chan_start(struct uaudio_chan *ch)
258285bad582SHans Petter Selasky {
258385bad582SHans Petter Selasky 	struct uaudio_softc *sc = ch->priv_sc;
258485bad582SHans Petter Selasky 
258585bad582SHans Petter Selasky 	/* make operation atomic */
258685bad582SHans Petter Selasky 	usb_proc_explore_lock(sc->sc_udev);
258785bad582SHans Petter Selasky 
258885bad582SHans Petter Selasky 	/* check if not running */
258985bad582SHans Petter Selasky 	if (ch->running == 0) {
259085bad582SHans Petter Selasky 	  	uint32_t temp;
259185bad582SHans Petter Selasky 
259285bad582SHans Petter Selasky 		/* get current buffer size */
259385bad582SHans Petter Selasky 		temp = 2 * uaudio_get_buffer_size(ch, ch->set_alt);
259485bad582SHans Petter Selasky 
259585bad582SHans Petter Selasky 		/* set running flag */
259685bad582SHans Petter Selasky 		ch->running = 1;
259785bad582SHans Petter Selasky 
259885bad582SHans Petter Selasky 		/* ensure the hardware buffer is reset */
259985bad582SHans Petter Selasky 		ch->start = ch->buf;
260085bad582SHans Petter Selasky 		ch->end = ch->buf + temp;
260185bad582SHans Petter Selasky 		ch->cur = ch->buf;
260285bad582SHans Petter Selasky 
260385bad582SHans Petter Selasky 		if (uaudio_chan_need_both(sc)) {
260485bad582SHans Petter Selasky 			/*
260585bad582SHans Petter Selasky 			 * Start both endpoints because of need for
260685bad582SHans Petter Selasky 			 * jitter information:
260785bad582SHans Petter Selasky 			 */
260885bad582SHans Petter Selasky 			uaudio_chan_start_sub(&sc->sc_rec_chan);
260985bad582SHans Petter Selasky 			uaudio_chan_start_sub(&sc->sc_play_chan);
261085bad582SHans Petter Selasky 		} else {
261185bad582SHans Petter Selasky 			uaudio_chan_start_sub(ch);
261285bad582SHans Petter Selasky 		}
261385bad582SHans Petter Selasky 	}
261485bad582SHans Petter Selasky 
261585bad582SHans Petter Selasky 	/* exit atomic operation */
261685bad582SHans Petter Selasky 	usb_proc_explore_unlock(sc->sc_udev);
261785bad582SHans Petter Selasky }
261885bad582SHans Petter Selasky 
261985bad582SHans Petter Selasky static void
262085bad582SHans Petter Selasky uaudio_chan_stop_sub(struct uaudio_chan *ch)
26213a3f90c6SAndrew Thompson {
2622455a367fSHans Petter Selasky 	struct uaudio_softc *sc = ch->priv_sc;
2623455a367fSHans Petter Selasky 	int do_stop = 0;
2624455a367fSHans Petter Selasky 
2625455a367fSHans Petter Selasky 	if (ch->operation != CHAN_OP_DRAIN) {
2626455a367fSHans Petter Selasky 		if (ch->cur_alt == ch->set_alt &&
262785bad582SHans Petter Selasky 		    ch->operation == CHAN_OP_NONE &&
262885bad582SHans Petter Selasky 		    mtx_owned(ch->pcm_mtx) != 0) {
2629455a367fSHans Petter Selasky 			/* save doing the explore task */
2630455a367fSHans Petter Selasky 			do_stop = 1;
2631455a367fSHans Petter Selasky 		} else {
2632455a367fSHans Petter Selasky 			ch->operation = CHAN_OP_STOP;
2633455a367fSHans Petter Selasky 			(void)usb_proc_explore_msignal(sc->sc_udev,
2634455a367fSHans Petter Selasky 			    &sc->sc_config_msg[0], &sc->sc_config_msg[1]);
2635455a367fSHans Petter Selasky 		}
2636455a367fSHans Petter Selasky 	}
2637455a367fSHans Petter Selasky 	if (do_stop) {
2638a593f6b8SAndrew Thompson 		usbd_transfer_stop(ch->xfer[0]);
2639a593f6b8SAndrew Thompson 		usbd_transfer_stop(ch->xfer[1]);
2640455a367fSHans Petter Selasky 	}
264185bad582SHans Petter Selasky }
264285bad582SHans Petter Selasky 
264385bad582SHans Petter Selasky void
264485bad582SHans Petter Selasky uaudio_chan_stop(struct uaudio_chan *ch)
264585bad582SHans Petter Selasky {
264685bad582SHans Petter Selasky 	struct uaudio_softc *sc = ch->priv_sc;
264785bad582SHans Petter Selasky 
264885bad582SHans Petter Selasky 	/* make operation atomic */
264985bad582SHans Petter Selasky 	usb_proc_explore_lock(sc->sc_udev);
265085bad582SHans Petter Selasky 
265185bad582SHans Petter Selasky 	/* check if running */
265285bad582SHans Petter Selasky 	if (ch->running != 0) {
265385bad582SHans Petter Selasky 		/* clear running flag */
265485bad582SHans Petter Selasky 		ch->running = 0;
265585bad582SHans Petter Selasky 
265685bad582SHans Petter Selasky 		if (uaudio_chan_need_both(sc)) {
265785bad582SHans Petter Selasky 			/*
265885bad582SHans Petter Selasky 			 * Leave the endpoints running because we need
265985bad582SHans Petter Selasky 			 * information about jitter!
266085bad582SHans Petter Selasky 			 */
266185bad582SHans Petter Selasky 		} else if (uaudio_chan_need_none(sc)) {
266285bad582SHans Petter Selasky 			/*
266385bad582SHans Petter Selasky 			 * Stop both endpoints in case the one was used for
266485bad582SHans Petter Selasky 			 * jitter information:
266585bad582SHans Petter Selasky 			 */
266685bad582SHans Petter Selasky 			uaudio_chan_stop_sub(&sc->sc_rec_chan);
266785bad582SHans Petter Selasky 			uaudio_chan_stop_sub(&sc->sc_play_chan);
266885bad582SHans Petter Selasky 		} else {
266985bad582SHans Petter Selasky 			uaudio_chan_stop_sub(ch);
267085bad582SHans Petter Selasky 		}
267185bad582SHans Petter Selasky 	}
267285bad582SHans Petter Selasky 
267385bad582SHans Petter Selasky 	/* exit atomic operation */
267485bad582SHans Petter Selasky 	usb_proc_explore_unlock(sc->sc_udev);
26753a3f90c6SAndrew Thompson }
26763a3f90c6SAndrew Thompson 
26773a3f90c6SAndrew Thompson /*========================================================================*
26783a3f90c6SAndrew Thompson  * AC - Audio Controller - routines
26793a3f90c6SAndrew Thompson  *========================================================================*/
26803a3f90c6SAndrew Thompson 
2681902514f6SHans Petter Selasky static int
2682902514f6SHans Petter Selasky uaudio_mixer_sysctl_handler(SYSCTL_HANDLER_ARGS)
2683902514f6SHans Petter Selasky {
2684902514f6SHans Petter Selasky 	struct uaudio_softc *sc;
2685902514f6SHans Petter Selasky 	struct uaudio_mixer_node *pmc;
2686902514f6SHans Petter Selasky 	int hint;
2687902514f6SHans Petter Selasky 	int error;
2688902514f6SHans Petter Selasky 	int temp = 0;
2689902514f6SHans Petter Selasky 	int chan = 0;
2690902514f6SHans Petter Selasky 
2691902514f6SHans Petter Selasky 	sc = (struct uaudio_softc *)oidp->oid_arg1;
2692902514f6SHans Petter Selasky 	hint = oidp->oid_arg2;
2693902514f6SHans Petter Selasky 
2694902514f6SHans Petter Selasky 	if (sc->sc_mixer_lock == NULL)
2695902514f6SHans Petter Selasky 		return (ENXIO);
2696902514f6SHans Petter Selasky 
2697902514f6SHans Petter Selasky 	/* lookup mixer node */
2698902514f6SHans Petter Selasky 
2699902514f6SHans Petter Selasky 	mtx_lock(sc->sc_mixer_lock);
2700902514f6SHans Petter Selasky 	for (pmc = sc->sc_mixer_root; pmc != NULL; pmc = pmc->next) {
2701902514f6SHans Petter Selasky 		for (chan = 0; chan != (int)pmc->nchan; chan++) {
2702902514f6SHans Petter Selasky 			if (pmc->wValue[chan] != -1 &&
2703902514f6SHans Petter Selasky 			    pmc->wValue[chan] == hint) {
2704902514f6SHans Petter Selasky 				temp = pmc->wData[chan];
2705902514f6SHans Petter Selasky 				goto found;
2706902514f6SHans Petter Selasky 			}
2707902514f6SHans Petter Selasky 		}
2708902514f6SHans Petter Selasky 	}
2709902514f6SHans Petter Selasky found:
2710902514f6SHans Petter Selasky 	mtx_unlock(sc->sc_mixer_lock);
2711902514f6SHans Petter Selasky 
2712902514f6SHans Petter Selasky 	error = sysctl_handle_int(oidp, &temp, 0, req);
2713902514f6SHans Petter Selasky 	if (error != 0 || req->newptr == NULL)
2714902514f6SHans Petter Selasky 		return (error);
2715902514f6SHans Petter Selasky 
2716902514f6SHans Petter Selasky 	/* update mixer value */
2717902514f6SHans Petter Selasky 
2718902514f6SHans Petter Selasky 	mtx_lock(sc->sc_mixer_lock);
2719902514f6SHans Petter Selasky 	if (pmc != NULL &&
2720902514f6SHans Petter Selasky 	    temp >= pmc->minval &&
2721902514f6SHans Petter Selasky 	    temp <= pmc->maxval) {
2722902514f6SHans Petter Selasky 
2723902514f6SHans Petter Selasky 		pmc->wData[chan] = temp;
2724902514f6SHans Petter Selasky 		pmc->update[(chan / 8)] |= (1 << (chan % 8));
2725902514f6SHans Petter Selasky 
2726902514f6SHans Petter Selasky 		/* start the transfer, if not already started */
2727902514f6SHans Petter Selasky 		usbd_transfer_start(sc->sc_mixer_xfer[0]);
2728902514f6SHans Petter Selasky 	}
2729902514f6SHans Petter Selasky 	mtx_unlock(sc->sc_mixer_lock);
2730902514f6SHans Petter Selasky 
2731902514f6SHans Petter Selasky 	return (0);
2732902514f6SHans Petter Selasky }
2733902514f6SHans Petter Selasky 
2734902514f6SHans Petter Selasky static void
2735902514f6SHans Petter Selasky uaudio_mixer_ctl_free(struct uaudio_softc *sc)
2736902514f6SHans Petter Selasky {
2737902514f6SHans Petter Selasky 	struct uaudio_mixer_node *p_mc;
2738902514f6SHans Petter Selasky 
2739902514f6SHans Petter Selasky 	while ((p_mc = sc->sc_mixer_root) != NULL) {
2740902514f6SHans Petter Selasky 		sc->sc_mixer_root = p_mc->next;
2741902514f6SHans Petter Selasky 		free(p_mc, M_USBDEV);
2742902514f6SHans Petter Selasky 	}
2743902514f6SHans Petter Selasky }
2744902514f6SHans Petter Selasky 
2745902514f6SHans Petter Selasky static void
2746902514f6SHans Petter Selasky uaudio_mixer_register_sysctl(struct uaudio_softc *sc, device_t dev)
2747902514f6SHans Petter Selasky {
2748902514f6SHans Petter Selasky 	struct uaudio_mixer_node *pmc;
2749902514f6SHans Petter Selasky 	struct sysctl_oid *mixer_tree;
2750902514f6SHans Petter Selasky 	struct sysctl_oid *control_tree;
2751902514f6SHans Petter Selasky 	char buf[32];
2752902514f6SHans Petter Selasky 	int chan;
2753902514f6SHans Petter Selasky 	int n;
2754902514f6SHans Petter Selasky 
2755ff4d5953SHans Petter Selasky 	mixer_tree = SYSCTL_ADD_NODE(device_get_sysctl_ctx(dev),
2756902514f6SHans Petter Selasky 	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "mixer",
2757902514f6SHans Petter Selasky 	    CTLFLAG_RD, NULL, "");
2758902514f6SHans Petter Selasky 
2759902514f6SHans Petter Selasky 	if (mixer_tree == NULL)
2760902514f6SHans Petter Selasky 		return;
2761902514f6SHans Petter Selasky 
2762902514f6SHans Petter Selasky 	for (n = 0, pmc = sc->sc_mixer_root; pmc != NULL;
2763902514f6SHans Petter Selasky 	    pmc = pmc->next, n++) {
2764902514f6SHans Petter Selasky 
2765902514f6SHans Petter Selasky 		for (chan = 0; chan < pmc->nchan; chan++) {
2766902514f6SHans Petter Selasky 
2767902514f6SHans Petter Selasky 			if (pmc->nchan > 1) {
2768902514f6SHans Petter Selasky 				snprintf(buf, sizeof(buf), "%s_%d_%d",
2769902514f6SHans Petter Selasky 				    pmc->name, n, chan);
2770902514f6SHans Petter Selasky 			} else {
2771902514f6SHans Petter Selasky 				snprintf(buf, sizeof(buf), "%s_%d",
2772902514f6SHans Petter Selasky 				    pmc->name, n);
2773902514f6SHans Petter Selasky 			}
2774902514f6SHans Petter Selasky 
2775ff4d5953SHans Petter Selasky 			control_tree = SYSCTL_ADD_NODE(device_get_sysctl_ctx(dev),
2776902514f6SHans Petter Selasky 			    SYSCTL_CHILDREN(mixer_tree), OID_AUTO, buf,
2777ff4d5953SHans Petter Selasky 			    CTLFLAG_RD, NULL, "Mixer control nodes");
2778902514f6SHans Petter Selasky 
2779902514f6SHans Petter Selasky 			if (control_tree == NULL)
2780902514f6SHans Petter Selasky 				continue;
2781902514f6SHans Petter Selasky 
2782ff4d5953SHans Petter Selasky 			SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
2783902514f6SHans Petter Selasky 			    SYSCTL_CHILDREN(control_tree),
2784ece4b0bdSHans Petter Selasky 			    OID_AUTO, "val", CTLTYPE_INT | CTLFLAG_RWTUN, sc,
2785902514f6SHans Petter Selasky 			    pmc->wValue[chan],
2786902514f6SHans Petter Selasky 			    uaudio_mixer_sysctl_handler, "I", "Current value");
2787902514f6SHans Petter Selasky 
2788ff4d5953SHans Petter Selasky 			SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
2789902514f6SHans Petter Selasky 			    SYSCTL_CHILDREN(control_tree),
2790902514f6SHans Petter Selasky 			    OID_AUTO, "min", CTLFLAG_RD, 0, pmc->minval,
2791902514f6SHans Petter Selasky 			    "Minimum value");
2792902514f6SHans Petter Selasky 
2793ff4d5953SHans Petter Selasky 			SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
2794902514f6SHans Petter Selasky 			    SYSCTL_CHILDREN(control_tree),
2795902514f6SHans Petter Selasky 			    OID_AUTO, "max", CTLFLAG_RD, 0, pmc->maxval,
2796902514f6SHans Petter Selasky 			    "Maximum value");
2797902514f6SHans Petter Selasky 
2798ff4d5953SHans Petter Selasky 			SYSCTL_ADD_STRING(device_get_sysctl_ctx(dev),
2799902514f6SHans Petter Selasky 			    SYSCTL_CHILDREN(control_tree),
2800902514f6SHans Petter Selasky 			    OID_AUTO, "desc", CTLFLAG_RD, pmc->desc, 0,
2801902514f6SHans Petter Selasky 			    "Description");
2802902514f6SHans Petter Selasky 		}
2803902514f6SHans Petter Selasky 	}
2804902514f6SHans Petter Selasky }
2805902514f6SHans Petter Selasky 
2806ff4d5953SHans Petter Selasky /* M-Audio FastTrack Ultra Mixer Description */
2807ff4d5953SHans Petter Selasky /* Origin: Linux USB Audio driver */
2808ff4d5953SHans Petter Selasky static void
2809ff4d5953SHans Petter Selasky uaudio_mixer_controls_create_ftu(struct uaudio_softc *sc)
2810ff4d5953SHans Petter Selasky {
2811ff4d5953SHans Petter Selasky 	int chx;
2812ff4d5953SHans Petter Selasky 	int chy;
2813ff4d5953SHans Petter Selasky 
2814f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
2815f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no);
2816f7e62ad0SHans Petter Selasky 	MIX(sc).wValue[0] = MAKE_WORD(8, 0);
2817f7e62ad0SHans Petter Selasky 	MIX(sc).class = UAC_OUTPUT;
2818f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_UNSIGNED_16;
2819f7e62ad0SHans Petter Selasky 	MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
2820f7e62ad0SHans Petter Selasky 	MIX(sc).name = "effect";
2821f7e62ad0SHans Petter Selasky 	MIX(sc).minval = 0;
2822f7e62ad0SHans Petter Selasky 	MIX(sc).maxval = 7;
2823f7e62ad0SHans Petter Selasky 	MIX(sc).mul = 7;
2824f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
2825f7e62ad0SHans Petter Selasky 	MIX(sc).update[0] = 1;
2826f7e62ad0SHans Petter Selasky 	strlcpy(MIX(sc).desc, "Room1,2,3,Hall1,2,Plate,Delay,Echo", sizeof(MIX(sc).desc));
2827f7e62ad0SHans Petter Selasky 	uaudio_mixer_add_ctl_sub(sc, &MIX(sc));
2828ff4d5953SHans Petter Selasky 
2829f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
2830f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(5, sc->sc_mixer_iface_no);
2831ff4d5953SHans Petter Selasky 
2832ff4d5953SHans Petter Selasky 	for (chx = 0; chx != 8; chx++) {
2833ff4d5953SHans Petter Selasky 		for (chy = 0; chy != 8; chy++) {
2834ff4d5953SHans Petter Selasky 
2835f7e62ad0SHans Petter Selasky 			MIX(sc).wValue[0] = MAKE_WORD(chx + 1, chy + 1);
2836f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_16;
2837f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
2838f7e62ad0SHans Petter Selasky 			MIX(sc).name = "mix_rec";
2839f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = 1;
2840f7e62ad0SHans Petter Selasky 			MIX(sc).update[0] = 1;
2841f7e62ad0SHans Petter Selasky 			MIX(sc).val_default = 0;
2842f7e62ad0SHans Petter Selasky 			snprintf(MIX(sc).desc, sizeof(MIX(sc).desc),
2843ff4d5953SHans Petter Selasky 			    "AIn%d - Out%d Record Volume", chy + 1, chx + 1);
2844ff4d5953SHans Petter Selasky 
2845f7e62ad0SHans Petter Selasky 			uaudio_mixer_add_ctl(sc, &MIX(sc));
2846ff4d5953SHans Petter Selasky 
2847f7e62ad0SHans Petter Selasky 			MIX(sc).wValue[0] = MAKE_WORD(chx + 1, chy + 1 + 8);
2848f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_16;
2849f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
2850f7e62ad0SHans Petter Selasky 			MIX(sc).name = "mix_play";
2851f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = 1;
2852f7e62ad0SHans Petter Selasky 			MIX(sc).update[0] = 1;
2853f7e62ad0SHans Petter Selasky 			MIX(sc).val_default = (chx == chy) ? 2 : 0;
2854f7e62ad0SHans Petter Selasky 			snprintf(MIX(sc).desc, sizeof(MIX(sc).desc),
2855ff4d5953SHans Petter Selasky 			    "DIn%d - Out%d Playback Volume", chy + 1, chx + 1);
2856ff4d5953SHans Petter Selasky 
2857f7e62ad0SHans Petter Selasky 			uaudio_mixer_add_ctl(sc, &MIX(sc));
2858ff4d5953SHans Petter Selasky 		}
2859ff4d5953SHans Petter Selasky 	}
2860ff4d5953SHans Petter Selasky 
2861f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
2862f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no);
2863f7e62ad0SHans Petter Selasky 	MIX(sc).wValue[0] = MAKE_WORD(2, 0);
2864f7e62ad0SHans Petter Selasky 	MIX(sc).class = UAC_OUTPUT;
2865f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_SIGNED_8;
2866f7e62ad0SHans Petter Selasky 	MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
2867f7e62ad0SHans Petter Selasky 	MIX(sc).name = "effect_vol";
2868f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
2869f7e62ad0SHans Petter Selasky 	MIX(sc).update[0] = 1;
2870f7e62ad0SHans Petter Selasky 	MIX(sc).minval = 0;
2871f7e62ad0SHans Petter Selasky 	MIX(sc).maxval = 0x7f;
2872f7e62ad0SHans Petter Selasky 	MIX(sc).mul = 0x7f;
2873f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
2874f7e62ad0SHans Petter Selasky 	MIX(sc).update[0] = 1;
2875f7e62ad0SHans Petter Selasky 	strlcpy(MIX(sc).desc, "Effect Volume", sizeof(MIX(sc).desc));
2876f7e62ad0SHans Petter Selasky 	uaudio_mixer_add_ctl_sub(sc, &MIX(sc));
2877ff4d5953SHans Petter Selasky 
2878f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
2879f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no);
2880f7e62ad0SHans Petter Selasky 	MIX(sc).wValue[0] = MAKE_WORD(3, 0);
2881f7e62ad0SHans Petter Selasky 	MIX(sc).class = UAC_OUTPUT;
2882f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_SIGNED_16;
2883f7e62ad0SHans Petter Selasky 	MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
2884f7e62ad0SHans Petter Selasky 	MIX(sc).name = "effect_dur";
2885f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
2886f7e62ad0SHans Petter Selasky 	MIX(sc).update[0] = 1;
2887f7e62ad0SHans Petter Selasky 	MIX(sc).minval = 0;
2888f7e62ad0SHans Petter Selasky 	MIX(sc).maxval = 0x7f00;
2889f7e62ad0SHans Petter Selasky 	MIX(sc).mul = 0x7f00;
2890f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
2891f7e62ad0SHans Petter Selasky 	MIX(sc).update[0] = 1;
2892f7e62ad0SHans Petter Selasky 	strlcpy(MIX(sc).desc, "Effect Duration", sizeof(MIX(sc).desc));
2893f7e62ad0SHans Petter Selasky 	uaudio_mixer_add_ctl_sub(sc, &MIX(sc));
2894ff4d5953SHans Petter Selasky 
2895f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
2896f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no);
2897f7e62ad0SHans Petter Selasky 	MIX(sc).wValue[0] = MAKE_WORD(4, 0);
2898f7e62ad0SHans Petter Selasky 	MIX(sc).class = UAC_OUTPUT;
2899f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_SIGNED_8;
2900f7e62ad0SHans Petter Selasky 	MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
2901f7e62ad0SHans Petter Selasky 	MIX(sc).name = "effect_fb";
2902f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
2903f7e62ad0SHans Petter Selasky 	MIX(sc).update[0] = 1;
2904f7e62ad0SHans Petter Selasky 	MIX(sc).minval = 0;
2905f7e62ad0SHans Petter Selasky 	MIX(sc).maxval = 0x7f;
2906f7e62ad0SHans Petter Selasky 	MIX(sc).mul = 0x7f;
2907f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
2908f7e62ad0SHans Petter Selasky 	MIX(sc).update[0] = 1;
2909f7e62ad0SHans Petter Selasky 	strlcpy(MIX(sc).desc, "Effect Feedback Volume", sizeof(MIX(sc).desc));
2910f7e62ad0SHans Petter Selasky 	uaudio_mixer_add_ctl_sub(sc, &MIX(sc));
2911ff4d5953SHans Petter Selasky 
2912f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
2913f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(7, sc->sc_mixer_iface_no);
2914ff4d5953SHans Petter Selasky 	for (chy = 0; chy != 4; chy++) {
2915ff4d5953SHans Petter Selasky 
2916f7e62ad0SHans Petter Selasky 		MIX(sc).wValue[0] = MAKE_WORD(7, chy + 1);
2917f7e62ad0SHans Petter Selasky 		MIX(sc).type = MIX_SIGNED_16;
2918f7e62ad0SHans Petter Selasky 		MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
2919f7e62ad0SHans Petter Selasky 		MIX(sc).name = "effect_ret";
2920f7e62ad0SHans Petter Selasky 		MIX(sc).nchan = 1;
2921f7e62ad0SHans Petter Selasky 		MIX(sc).update[0] = 1;
2922f7e62ad0SHans Petter Selasky 		snprintf(MIX(sc).desc, sizeof(MIX(sc).desc),
2923ff4d5953SHans Petter Selasky 		    "Effect Return %d Volume", chy + 1);
2924ff4d5953SHans Petter Selasky 
2925f7e62ad0SHans Petter Selasky 		uaudio_mixer_add_ctl(sc, &MIX(sc));
2926ff4d5953SHans Petter Selasky 	}
2927ff4d5953SHans Petter Selasky 
2928f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
2929f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(5, sc->sc_mixer_iface_no);
2930ff4d5953SHans Petter Selasky 
2931ff4d5953SHans Petter Selasky 	for (chy = 0; chy != 8; chy++) {
2932f7e62ad0SHans Petter Selasky 		MIX(sc).wValue[0] = MAKE_WORD(9, chy + 1);
2933f7e62ad0SHans Petter Selasky 		MIX(sc).type = MIX_SIGNED_16;
2934f7e62ad0SHans Petter Selasky 		MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
2935f7e62ad0SHans Petter Selasky 		MIX(sc).name = "effect_send";
2936f7e62ad0SHans Petter Selasky 		MIX(sc).nchan = 1;
2937f7e62ad0SHans Petter Selasky 		MIX(sc).update[0] = 1;
2938f7e62ad0SHans Petter Selasky 		snprintf(MIX(sc).desc, sizeof(MIX(sc).desc),
2939ff4d5953SHans Petter Selasky 		    "Effect Send AIn%d Volume", chy + 1);
2940ff4d5953SHans Petter Selasky 
2941f7e62ad0SHans Petter Selasky 		uaudio_mixer_add_ctl(sc, &MIX(sc));
2942ff4d5953SHans Petter Selasky 
2943113336eeSHans Petter Selasky 		MIX(sc).wValue[0] = MAKE_WORD(9, chy + 1 + 8);
2944f7e62ad0SHans Petter Selasky 		MIX(sc).type = MIX_SIGNED_16;
2945f7e62ad0SHans Petter Selasky 		MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
2946f7e62ad0SHans Petter Selasky 		MIX(sc).name = "effect_send";
2947f7e62ad0SHans Petter Selasky 		MIX(sc).nchan = 1;
2948f7e62ad0SHans Petter Selasky 		MIX(sc).update[0] = 1;
2949f7e62ad0SHans Petter Selasky 		snprintf(MIX(sc).desc, sizeof(MIX(sc).desc),
2950113336eeSHans Petter Selasky 		    "Effect Send DIn%d Volume", chy + 1);
2951ff4d5953SHans Petter Selasky 
2952f7e62ad0SHans Petter Selasky 		uaudio_mixer_add_ctl(sc, &MIX(sc));
2953ff4d5953SHans Petter Selasky 	}
2954ff4d5953SHans Petter Selasky }
2955ff4d5953SHans Petter Selasky 
2956902514f6SHans Petter Selasky static void
2957902514f6SHans Petter Selasky uaudio_mixer_reload_all(struct uaudio_softc *sc)
2958902514f6SHans Petter Selasky {
2959902514f6SHans Petter Selasky 	struct uaudio_mixer_node *pmc;
2960902514f6SHans Petter Selasky 	int chan;
2961902514f6SHans Petter Selasky 
2962902514f6SHans Petter Selasky 	if (sc->sc_mixer_lock == NULL)
2963902514f6SHans Petter Selasky 		return;
2964902514f6SHans Petter Selasky 
2965902514f6SHans Petter Selasky 	mtx_lock(sc->sc_mixer_lock);
2966902514f6SHans Petter Selasky 	for (pmc = sc->sc_mixer_root; pmc != NULL; pmc = pmc->next) {
2967ff4d5953SHans Petter Selasky 		/* use reset defaults for non-oss controlled settings */
2968ff4d5953SHans Petter Selasky 		if (pmc->ctl == SOUND_MIXER_NRDEVICES)
2969ff4d5953SHans Petter Selasky 			continue;
2970902514f6SHans Petter Selasky 		for (chan = 0; chan < pmc->nchan; chan++)
2971902514f6SHans Petter Selasky 			pmc->update[chan / 8] |= (1 << (chan % 8));
2972902514f6SHans Petter Selasky 	}
2973902514f6SHans Petter Selasky 	usbd_transfer_start(sc->sc_mixer_xfer[0]);
297476b71212SHans Petter Selasky 
297576b71212SHans Petter Selasky 	/* start HID volume keys, if any */
297676b71212SHans Petter Selasky 	usbd_transfer_start(sc->sc_hid.xfer[0]);
2977902514f6SHans Petter Selasky 	mtx_unlock(sc->sc_mixer_lock);
2978902514f6SHans Petter Selasky }
2979902514f6SHans Petter Selasky 
29803a3f90c6SAndrew Thompson static void
29813a3f90c6SAndrew Thompson uaudio_mixer_add_ctl_sub(struct uaudio_softc *sc, struct uaudio_mixer_node *mc)
29823a3f90c6SAndrew Thompson {
29833a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *p_mc_new =
29843a3f90c6SAndrew Thompson 	    malloc(sizeof(*p_mc_new), M_USBDEV, M_WAITOK);
2985ff4d5953SHans Petter Selasky 	int ch;
29863a3f90c6SAndrew Thompson 
29876f068a43SHans Petter Selasky 	if (p_mc_new != NULL) {
29886f068a43SHans Petter Selasky 		memcpy(p_mc_new, mc, sizeof(*p_mc_new));
29893a3f90c6SAndrew Thompson 		p_mc_new->next = sc->sc_mixer_root;
29903a3f90c6SAndrew Thompson 		sc->sc_mixer_root = p_mc_new;
29913a3f90c6SAndrew Thompson 		sc->sc_mixer_count++;
2992ff4d5953SHans Petter Selasky 
2993ff4d5953SHans Petter Selasky 		/* set default value for all channels */
2994ff4d5953SHans Petter Selasky 		for (ch = 0; ch < p_mc_new->nchan; ch++) {
2995ff4d5953SHans Petter Selasky 			switch (p_mc_new->val_default) {
2996ff4d5953SHans Petter Selasky 			case 1:
299758e8ac5cSHans Petter Selasky 				/* 50% */
2998ff4d5953SHans Petter Selasky 				p_mc_new->wData[ch] = (p_mc_new->maxval + p_mc_new->minval) / 2;
2999ff4d5953SHans Petter Selasky 				break;
3000ff4d5953SHans Petter Selasky 			case 2:
300158e8ac5cSHans Petter Selasky 				/* 100% */
3002ff4d5953SHans Petter Selasky 				p_mc_new->wData[ch] = p_mc_new->maxval;
3003ff4d5953SHans Petter Selasky 				break;
3004ff4d5953SHans Petter Selasky 			default:
300558e8ac5cSHans Petter Selasky 				/* 0% */
3006ff4d5953SHans Petter Selasky 				p_mc_new->wData[ch] = p_mc_new->minval;
3007ff4d5953SHans Petter Selasky 				break;
3008ff4d5953SHans Petter Selasky 			}
3009ff4d5953SHans Petter Selasky 		}
30103a3f90c6SAndrew Thompson 	} else {
30113a3f90c6SAndrew Thompson 		DPRINTF("out of memory\n");
30123a3f90c6SAndrew Thompson 	}
30133a3f90c6SAndrew Thompson }
30143a3f90c6SAndrew Thompson 
30153a3f90c6SAndrew Thompson static void
30163a3f90c6SAndrew Thompson uaudio_mixer_add_ctl(struct uaudio_softc *sc, struct uaudio_mixer_node *mc)
30173a3f90c6SAndrew Thompson {
30183a3f90c6SAndrew Thompson 	int32_t res;
30193a3f90c6SAndrew Thompson 
30203a3f90c6SAndrew Thompson 	if (mc->class < UAC_NCLASSES) {
30213a3f90c6SAndrew Thompson 		DPRINTF("adding %s.%d\n",
30223a3f90c6SAndrew Thompson 		    uac_names[mc->class], mc->ctl);
30233a3f90c6SAndrew Thompson 	} else {
30243a3f90c6SAndrew Thompson 		DPRINTF("adding %d\n", mc->ctl);
30253a3f90c6SAndrew Thompson 	}
30263a3f90c6SAndrew Thompson 
30273a3f90c6SAndrew Thompson 	if (mc->type == MIX_ON_OFF) {
30283a3f90c6SAndrew Thompson 		mc->minval = 0;
30293a3f90c6SAndrew Thompson 		mc->maxval = 1;
30303a3f90c6SAndrew Thompson 	} else if (mc->type == MIX_SELECTOR) {
30313a3f90c6SAndrew Thompson 	} else {
30323a3f90c6SAndrew Thompson 
30333a3f90c6SAndrew Thompson 		/* determine min and max values */
30343a3f90c6SAndrew Thompson 
3035e2524b2eSHans Petter Selasky 		mc->minval = uaudio_mixer_get(sc->sc_udev,
3036e2524b2eSHans Petter Selasky 		    sc->sc_audio_rev, GET_MIN, mc);
3037e2524b2eSHans Petter Selasky 		mc->maxval = uaudio_mixer_get(sc->sc_udev,
3038e2524b2eSHans Petter Selasky 		    sc->sc_audio_rev, GET_MAX, mc);
30393a3f90c6SAndrew Thompson 
3040b029f6bbSAndrew Thompson 		/* check if max and min was swapped */
30413a3f90c6SAndrew Thompson 
30423a3f90c6SAndrew Thompson 		if (mc->maxval < mc->minval) {
3043b029f6bbSAndrew Thompson 			res = mc->maxval;
30443a3f90c6SAndrew Thompson 			mc->maxval = mc->minval;
3045b029f6bbSAndrew Thompson 			mc->minval = res;
30463a3f90c6SAndrew Thompson 		}
3047b029f6bbSAndrew Thompson 
3048b029f6bbSAndrew Thompson 		/* compute value range */
3049b029f6bbSAndrew Thompson 		mc->mul = mc->maxval - mc->minval;
3050b029f6bbSAndrew Thompson 		if (mc->mul == 0)
3051b029f6bbSAndrew Thompson 			mc->mul = 1;
3052b029f6bbSAndrew Thompson 
3053b029f6bbSAndrew Thompson 		/* compute value alignment */
3054e2524b2eSHans Petter Selasky 		res = uaudio_mixer_get(sc->sc_udev,
3055e2524b2eSHans Petter Selasky 		    sc->sc_audio_rev, GET_RES, mc);
30567fb43570SAndrew Thompson 
30577fb43570SAndrew Thompson 		DPRINTF("Resolution = %d\n", (int)res);
3058b029f6bbSAndrew Thompson 	}
3059b029f6bbSAndrew Thompson 
30603a3f90c6SAndrew Thompson 	uaudio_mixer_add_ctl_sub(sc, mc);
30613a3f90c6SAndrew Thompson 
3062b850ecc1SAndrew Thompson #ifdef USB_DEBUG
30633a3f90c6SAndrew Thompson 	if (uaudio_debug > 2) {
30643a3f90c6SAndrew Thompson 		uint8_t i;
30653a3f90c6SAndrew Thompson 
30663a3f90c6SAndrew Thompson 		for (i = 0; i < mc->nchan; i++) {
30673a3f90c6SAndrew Thompson 			DPRINTF("[mix] wValue=%04x\n", mc->wValue[0]);
30683a3f90c6SAndrew Thompson 		}
30693a3f90c6SAndrew Thompson 		DPRINTF("[mix] wIndex=%04x type=%d ctl='%d' "
30703a3f90c6SAndrew Thompson 		    "min=%d max=%d\n",
30713a3f90c6SAndrew Thompson 		    mc->wIndex, mc->type, mc->ctl,
30723a3f90c6SAndrew Thompson 		    mc->minval, mc->maxval);
30733a3f90c6SAndrew Thompson 	}
30743a3f90c6SAndrew Thompson #endif
30753a3f90c6SAndrew Thompson }
30763a3f90c6SAndrew Thompson 
30773a3f90c6SAndrew Thompson static void
30783a3f90c6SAndrew Thompson uaudio_mixer_add_mixer(struct uaudio_softc *sc,
30793a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
30803a3f90c6SAndrew Thompson {
3081e2524b2eSHans Petter Selasky 	const struct usb_audio_mixer_unit_0 *d0 = iot[id].u.mu_v1;
30824c21be9bSRebecca Cran 	const struct usb_audio_mixer_unit_1 *d1;
30833a3f90c6SAndrew Thompson 
30843a3f90c6SAndrew Thompson 	uint32_t bno;			/* bit number */
30853a3f90c6SAndrew Thompson 	uint32_t p;			/* bit number accumulator */
30863a3f90c6SAndrew Thompson 	uint32_t mo;			/* matching outputs */
30873a3f90c6SAndrew Thompson 	uint32_t mc;			/* matching channels */
30883a3f90c6SAndrew Thompson 	uint32_t ichs;			/* input channels */
30893a3f90c6SAndrew Thompson 	uint32_t ochs;			/* output channels */
30903a3f90c6SAndrew Thompson 	uint32_t c;
30913a3f90c6SAndrew Thompson 	uint32_t chs;			/* channels */
30923a3f90c6SAndrew Thompson 	uint32_t i;
30933a3f90c6SAndrew Thompson 	uint32_t o;
30943a3f90c6SAndrew Thompson 
30953a3f90c6SAndrew Thompson 	DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n",
30963a3f90c6SAndrew Thompson 	    d0->bUnitId, d0->bNrInPins);
30973a3f90c6SAndrew Thompson 
30983a3f90c6SAndrew Thompson 	/* compute the number of input channels */
30993a3f90c6SAndrew Thompson 
31003a3f90c6SAndrew Thompson 	ichs = 0;
31013a3f90c6SAndrew Thompson 	for (i = 0; i < d0->bNrInPins; i++) {
3102e2524b2eSHans Petter Selasky 		ichs += uaudio_mixer_get_cluster(
3103e2524b2eSHans Petter Selasky 		    d0->baSourceId[i], iot).bNrChannels;
31043a3f90c6SAndrew Thompson 	}
31053a3f90c6SAndrew Thompson 
31063a3f90c6SAndrew Thompson 	d1 = (const void *)(d0->baSourceId + d0->bNrInPins);
31073a3f90c6SAndrew Thompson 
31083a3f90c6SAndrew Thompson 	/* and the number of output channels */
31093a3f90c6SAndrew Thompson 
31103a3f90c6SAndrew Thompson 	ochs = d1->bNrChannels;
31113a3f90c6SAndrew Thompson 
31123a3f90c6SAndrew Thompson 	DPRINTFN(3, "ichs=%d ochs=%d\n", ichs, ochs);
31133a3f90c6SAndrew Thompson 
3114f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
31153a3f90c6SAndrew Thompson 
3116f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no);
3117f7e62ad0SHans Petter Selasky 	uaudio_mixer_determine_class(&iot[id], &MIX(sc));
3118f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_SIGNED_16;
31193a3f90c6SAndrew Thompson 
3120e2524b2eSHans Petter Selasky 	if (uaudio_mixer_verify_desc(d0, ((ichs * ochs) + 7) / 8) == NULL)
31213a3f90c6SAndrew Thompson 		return;
3122e2524b2eSHans Petter Selasky 
31233a3f90c6SAndrew Thompson 	for (p = i = 0; i < d0->bNrInPins; i++) {
3124e2524b2eSHans Petter Selasky 		chs = uaudio_mixer_get_cluster(
3125e2524b2eSHans Petter Selasky 		    d0->baSourceId[i], iot).bNrChannels;
31263a3f90c6SAndrew Thompson 		mc = 0;
31273a3f90c6SAndrew Thompson 		for (c = 0; c < chs; c++) {
31283a3f90c6SAndrew Thompson 			mo = 0;
31293a3f90c6SAndrew Thompson 			for (o = 0; o < ochs; o++) {
31303a3f90c6SAndrew Thompson 				bno = ((p + c) * ochs) + o;
3131e2524b2eSHans Petter Selasky 				if (BIT_TEST(d1->bmControls, bno))
31323a3f90c6SAndrew Thompson 					mo++;
31333a3f90c6SAndrew Thompson 			}
3134e2524b2eSHans Petter Selasky 			if (mo == 1)
31353a3f90c6SAndrew Thompson 				mc++;
31363a3f90c6SAndrew Thompson 		}
31373a3f90c6SAndrew Thompson 		if ((mc == chs) && (chs <= MIX_MAX_CHAN)) {
31383a3f90c6SAndrew Thompson 
31393a3f90c6SAndrew Thompson 			/* repeat bit-scan */
31403a3f90c6SAndrew Thompson 
31413a3f90c6SAndrew Thompson 			mc = 0;
31423a3f90c6SAndrew Thompson 			for (c = 0; c < chs; c++) {
31433a3f90c6SAndrew Thompson 				for (o = 0; o < ochs; o++) {
31443a3f90c6SAndrew Thompson 					bno = ((p + c) * ochs) + o;
3145e2524b2eSHans Petter Selasky 					if (BIT_TEST(d1->bmControls, bno))
3146f7e62ad0SHans Petter Selasky 						MIX(sc).wValue[mc++] = MAKE_WORD(p + c + 1, o + 1);
31473a3f90c6SAndrew Thompson 				}
31483a3f90c6SAndrew Thompson 			}
3149f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = chs;
3150f7e62ad0SHans Petter Selasky 			uaudio_mixer_add_ctl(sc, &MIX(sc));
3151e2524b2eSHans Petter Selasky 		}
3152e2524b2eSHans Petter Selasky 		p += chs;
3153e2524b2eSHans Petter Selasky 	}
3154e2524b2eSHans Petter Selasky }
3155e2524b2eSHans Petter Selasky 
3156e2524b2eSHans Petter Selasky static void
3157e2524b2eSHans Petter Selasky uaudio20_mixer_add_mixer(struct uaudio_softc *sc,
3158e2524b2eSHans Petter Selasky     const struct uaudio_terminal_node *iot, int id)
3159e2524b2eSHans Petter Selasky {
3160e2524b2eSHans Petter Selasky 	const struct usb_audio20_mixer_unit_0 *d0 = iot[id].u.mu_v2;
3161e2524b2eSHans Petter Selasky 	const struct usb_audio20_mixer_unit_1 *d1;
3162e2524b2eSHans Petter Selasky 
3163e2524b2eSHans Petter Selasky 	uint32_t bno;			/* bit number */
3164e2524b2eSHans Petter Selasky 	uint32_t p;			/* bit number accumulator */
3165e2524b2eSHans Petter Selasky 	uint32_t mo;			/* matching outputs */
3166e2524b2eSHans Petter Selasky 	uint32_t mc;			/* matching channels */
3167e2524b2eSHans Petter Selasky 	uint32_t ichs;			/* input channels */
3168e2524b2eSHans Petter Selasky 	uint32_t ochs;			/* output channels */
3169e2524b2eSHans Petter Selasky 	uint32_t c;
3170e2524b2eSHans Petter Selasky 	uint32_t chs;			/* channels */
3171e2524b2eSHans Petter Selasky 	uint32_t i;
3172e2524b2eSHans Petter Selasky 	uint32_t o;
3173e2524b2eSHans Petter Selasky 
3174e2524b2eSHans Petter Selasky 	DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n",
3175e2524b2eSHans Petter Selasky 	    d0->bUnitId, d0->bNrInPins);
3176e2524b2eSHans Petter Selasky 
3177e2524b2eSHans Petter Selasky 	/* compute the number of input channels */
3178e2524b2eSHans Petter Selasky 
3179e2524b2eSHans Petter Selasky 	ichs = 0;
3180e2524b2eSHans Petter Selasky 	for (i = 0; i < d0->bNrInPins; i++) {
3181e2524b2eSHans Petter Selasky 		ichs += uaudio20_mixer_get_cluster(
3182e2524b2eSHans Petter Selasky 		    d0->baSourceId[i], iot).bNrChannels;
3183e2524b2eSHans Petter Selasky 	}
3184e2524b2eSHans Petter Selasky 
3185e2524b2eSHans Petter Selasky 	d1 = (const void *)(d0->baSourceId + d0->bNrInPins);
3186e2524b2eSHans Petter Selasky 
3187e2524b2eSHans Petter Selasky 	/* and the number of output channels */
3188e2524b2eSHans Petter Selasky 
3189e2524b2eSHans Petter Selasky 	ochs = d1->bNrChannels;
3190e2524b2eSHans Petter Selasky 
3191e2524b2eSHans Petter Selasky 	DPRINTFN(3, "ichs=%d ochs=%d\n", ichs, ochs);
3192e2524b2eSHans Petter Selasky 
3193f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
3194e2524b2eSHans Petter Selasky 
3195f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no);
3196f7e62ad0SHans Petter Selasky 	uaudio20_mixer_determine_class(&iot[id], &MIX(sc));
3197f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_SIGNED_16;
3198e2524b2eSHans Petter Selasky 
3199e2524b2eSHans Petter Selasky 	if (uaudio20_mixer_verify_desc(d0, ((ichs * ochs) + 7) / 8) == NULL)
3200e2524b2eSHans Petter Selasky 		return;
3201e2524b2eSHans Petter Selasky 
3202e2524b2eSHans Petter Selasky 	for (p = i = 0; i < d0->bNrInPins; i++) {
3203e2524b2eSHans Petter Selasky 		chs = uaudio20_mixer_get_cluster(
3204e2524b2eSHans Petter Selasky 		    d0->baSourceId[i], iot).bNrChannels;
3205e2524b2eSHans Petter Selasky 		mc = 0;
3206e2524b2eSHans Petter Selasky 		for (c = 0; c < chs; c++) {
3207e2524b2eSHans Petter Selasky 			mo = 0;
3208e2524b2eSHans Petter Selasky 			for (o = 0; o < ochs; o++) {
3209e2524b2eSHans Petter Selasky 				bno = ((p + c) * ochs) + o;
3210e2524b2eSHans Petter Selasky 				if (BIT_TEST(d1->bmControls, bno))
3211e2524b2eSHans Petter Selasky 					mo++;
3212e2524b2eSHans Petter Selasky 			}
3213e2524b2eSHans Petter Selasky 			if (mo == 1)
3214e2524b2eSHans Petter Selasky 				mc++;
3215e2524b2eSHans Petter Selasky 		}
3216e2524b2eSHans Petter Selasky 		if ((mc == chs) && (chs <= MIX_MAX_CHAN)) {
3217e2524b2eSHans Petter Selasky 
3218e2524b2eSHans Petter Selasky 			/* repeat bit-scan */
3219e2524b2eSHans Petter Selasky 
3220e2524b2eSHans Petter Selasky 			mc = 0;
3221e2524b2eSHans Petter Selasky 			for (c = 0; c < chs; c++) {
3222e2524b2eSHans Petter Selasky 				for (o = 0; o < ochs; o++) {
3223e2524b2eSHans Petter Selasky 					bno = ((p + c) * ochs) + o;
3224e2524b2eSHans Petter Selasky 					if (BIT_TEST(d1->bmControls, bno))
3225f7e62ad0SHans Petter Selasky 						MIX(sc).wValue[mc++] = MAKE_WORD(p + c + 1, o + 1);
3226e2524b2eSHans Petter Selasky 				}
3227e2524b2eSHans Petter Selasky 			}
3228f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = chs;
3229f7e62ad0SHans Petter Selasky 			uaudio_mixer_add_ctl(sc, &MIX(sc));
32303a3f90c6SAndrew Thompson 		}
32313a3f90c6SAndrew Thompson 		p += chs;
32323a3f90c6SAndrew Thompson 	}
32333a3f90c6SAndrew Thompson }
32343a3f90c6SAndrew Thompson 
32353a3f90c6SAndrew Thompson static void
32363a3f90c6SAndrew Thompson uaudio_mixer_add_selector(struct uaudio_softc *sc,
32373a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
32383a3f90c6SAndrew Thompson {
3239e2524b2eSHans Petter Selasky 	const struct usb_audio_selector_unit *d = iot[id].u.su_v1;
32403a3f90c6SAndrew Thompson 	uint16_t i;
32413a3f90c6SAndrew Thompson 
32423a3f90c6SAndrew Thompson 	DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n",
32433a3f90c6SAndrew Thompson 	    d->bUnitId, d->bNrInPins);
32443a3f90c6SAndrew Thompson 
3245902514f6SHans Petter Selasky 	if (d->bNrInPins == 0)
32463a3f90c6SAndrew Thompson 		return;
3247902514f6SHans Petter Selasky 
3248f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
32493a3f90c6SAndrew Thompson 
3250f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no);
3251f7e62ad0SHans Petter Selasky 	MIX(sc).wValue[0] = MAKE_WORD(0, 0);
3252f7e62ad0SHans Petter Selasky 	uaudio_mixer_determine_class(&iot[id], &MIX(sc));
3253f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
3254f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_SELECTOR;
3255f7e62ad0SHans Petter Selasky 	MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
3256f7e62ad0SHans Petter Selasky 	MIX(sc).minval = 1;
3257f7e62ad0SHans Petter Selasky 	MIX(sc).maxval = d->bNrInPins;
3258f7e62ad0SHans Petter Selasky 	MIX(sc).name = "selector";
3259902514f6SHans Petter Selasky 
3260902514f6SHans Petter Selasky 	i = d->baSourceId[d->bNrInPins];
3261902514f6SHans Petter Selasky 	if (i == 0 ||
3262902514f6SHans Petter Selasky 	    usbd_req_get_string_any(sc->sc_udev, NULL,
3263f7e62ad0SHans Petter Selasky 	    MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) {
3264f7e62ad0SHans Petter Selasky 		MIX(sc).desc[0] = 0;
3265902514f6SHans Petter Selasky 	}
32663a3f90c6SAndrew Thompson 
3267f7e62ad0SHans Petter Selasky 	if (MIX(sc).maxval > MAX_SELECTOR_INPUT_PIN) {
3268f7e62ad0SHans Petter Selasky 		MIX(sc).maxval = MAX_SELECTOR_INPUT_PIN;
32693a3f90c6SAndrew Thompson 	}
3270f7e62ad0SHans Petter Selasky 	MIX(sc).mul = (MIX(sc).maxval - MIX(sc).minval);
32713a3f90c6SAndrew Thompson 	for (i = 0; i < MAX_SELECTOR_INPUT_PIN; i++) {
3272f7e62ad0SHans Petter Selasky 		MIX(sc).slctrtype[i] = SOUND_MIXER_NRDEVICES;
32733a3f90c6SAndrew Thompson 	}
32743a3f90c6SAndrew Thompson 
3275f7e62ad0SHans Petter Selasky 	for (i = 0; i < MIX(sc).maxval; i++) {
3276f7e62ad0SHans Petter Selasky 		MIX(sc).slctrtype[i] = uaudio_mixer_feature_name(
3277f7e62ad0SHans Petter Selasky 		    &iot[d->baSourceId[i]], &MIX(sc));
3278e2524b2eSHans Petter Selasky 	}
3279e2524b2eSHans Petter Selasky 
3280f7e62ad0SHans Petter Selasky 	MIX(sc).class = 0;			/* not used */
3281e2524b2eSHans Petter Selasky 
3282f7e62ad0SHans Petter Selasky 	uaudio_mixer_add_ctl(sc, &MIX(sc));
3283e2524b2eSHans Petter Selasky }
3284e2524b2eSHans Petter Selasky 
3285e2524b2eSHans Petter Selasky static void
3286e2524b2eSHans Petter Selasky uaudio20_mixer_add_selector(struct uaudio_softc *sc,
3287e2524b2eSHans Petter Selasky     const struct uaudio_terminal_node *iot, int id)
3288e2524b2eSHans Petter Selasky {
3289e2524b2eSHans Petter Selasky 	const struct usb_audio20_selector_unit *d = iot[id].u.su_v2;
3290e2524b2eSHans Petter Selasky 	uint16_t i;
3291e2524b2eSHans Petter Selasky 
3292e2524b2eSHans Petter Selasky 	DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n",
3293e2524b2eSHans Petter Selasky 	    d->bUnitId, d->bNrInPins);
3294e2524b2eSHans Petter Selasky 
3295e2524b2eSHans Petter Selasky 	if (d->bNrInPins == 0)
3296e2524b2eSHans Petter Selasky 		return;
3297e2524b2eSHans Petter Selasky 
3298f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
3299e2524b2eSHans Petter Selasky 
3300f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no);
3301f7e62ad0SHans Petter Selasky 	MIX(sc).wValue[0] = MAKE_WORD(0, 0);
3302f7e62ad0SHans Petter Selasky 	uaudio20_mixer_determine_class(&iot[id], &MIX(sc));
3303f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
3304f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_SELECTOR;
3305f7e62ad0SHans Petter Selasky 	MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
3306f7e62ad0SHans Petter Selasky 	MIX(sc).minval = 1;
3307f7e62ad0SHans Petter Selasky 	MIX(sc).maxval = d->bNrInPins;
3308f7e62ad0SHans Petter Selasky 	MIX(sc).name = "selector";
3309902514f6SHans Petter Selasky 
3310902514f6SHans Petter Selasky 	i = d->baSourceId[d->bNrInPins];
3311902514f6SHans Petter Selasky 	if (i == 0 ||
3312902514f6SHans Petter Selasky 	    usbd_req_get_string_any(sc->sc_udev, NULL,
3313f7e62ad0SHans Petter Selasky 	    MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) {
3314f7e62ad0SHans Petter Selasky 		MIX(sc).desc[0] = 0;
3315902514f6SHans Petter Selasky 	}
3316e2524b2eSHans Petter Selasky 
3317f7e62ad0SHans Petter Selasky 	if (MIX(sc).maxval > MAX_SELECTOR_INPUT_PIN)
3318f7e62ad0SHans Petter Selasky 		MIX(sc).maxval = MAX_SELECTOR_INPUT_PIN;
3319e2524b2eSHans Petter Selasky 
3320f7e62ad0SHans Petter Selasky 	MIX(sc).mul = (MIX(sc).maxval - MIX(sc).minval);
3321e2524b2eSHans Petter Selasky 	for (i = 0; i < MAX_SELECTOR_INPUT_PIN; i++)
3322f7e62ad0SHans Petter Selasky 		MIX(sc).slctrtype[i] = SOUND_MIXER_NRDEVICES;
3323e2524b2eSHans Petter Selasky 
3324f7e62ad0SHans Petter Selasky 	for (i = 0; i < MIX(sc).maxval; i++) {
3325f7e62ad0SHans Petter Selasky 		MIX(sc).slctrtype[i] = uaudio20_mixer_feature_name(
3326f7e62ad0SHans Petter Selasky 		    &iot[d->baSourceId[i]], &MIX(sc));
33273a3f90c6SAndrew Thompson 	}
33283a3f90c6SAndrew Thompson 
3329f7e62ad0SHans Petter Selasky 	MIX(sc).class = 0;			/* not used */
33303a3f90c6SAndrew Thompson 
3331f7e62ad0SHans Petter Selasky 	uaudio_mixer_add_ctl(sc, &MIX(sc));
33323a3f90c6SAndrew Thompson }
33333a3f90c6SAndrew Thompson 
33343a3f90c6SAndrew Thompson static uint32_t
33354c21be9bSRebecca Cran uaudio_mixer_feature_get_bmaControls(const struct usb_audio_feature_unit *d,
33366d917491SHans Petter Selasky     uint8_t i)
33373a3f90c6SAndrew Thompson {
33383a3f90c6SAndrew Thompson 	uint32_t temp = 0;
33396d917491SHans Petter Selasky 	uint32_t offset = (i * d->bControlSize);
33403a3f90c6SAndrew Thompson 
33413a3f90c6SAndrew Thompson 	if (d->bControlSize > 0) {
33423a3f90c6SAndrew Thompson 		temp |= d->bmaControls[offset];
33433a3f90c6SAndrew Thompson 		if (d->bControlSize > 1) {
33443a3f90c6SAndrew Thompson 			temp |= d->bmaControls[offset + 1] << 8;
33453a3f90c6SAndrew Thompson 			if (d->bControlSize > 2) {
33463a3f90c6SAndrew Thompson 				temp |= d->bmaControls[offset + 2] << 16;
33473a3f90c6SAndrew Thompson 				if (d->bControlSize > 3) {
33483a3f90c6SAndrew Thompson 					temp |= d->bmaControls[offset + 3] << 24;
33493a3f90c6SAndrew Thompson 				}
33503a3f90c6SAndrew Thompson 			}
33513a3f90c6SAndrew Thompson 		}
33523a3f90c6SAndrew Thompson 	}
33533a3f90c6SAndrew Thompson 	return (temp);
33543a3f90c6SAndrew Thompson }
33553a3f90c6SAndrew Thompson 
33563a3f90c6SAndrew Thompson static void
33573a3f90c6SAndrew Thompson uaudio_mixer_add_feature(struct uaudio_softc *sc,
33583a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
33593a3f90c6SAndrew Thompson {
3360e2524b2eSHans Petter Selasky 	const struct usb_audio_feature_unit *d = iot[id].u.fu_v1;
33613a3f90c6SAndrew Thompson 	uint32_t fumask;
33623a3f90c6SAndrew Thompson 	uint32_t mmask;
33633a3f90c6SAndrew Thompson 	uint32_t cmask;
33643a3f90c6SAndrew Thompson 	uint16_t mixernumber;
33653a3f90c6SAndrew Thompson 	uint8_t nchan;
33663a3f90c6SAndrew Thompson 	uint8_t chan;
33673a3f90c6SAndrew Thompson 	uint8_t ctl;
33683a3f90c6SAndrew Thompson 	uint8_t i;
33693a3f90c6SAndrew Thompson 
3370902514f6SHans Petter Selasky 	if (d->bControlSize == 0)
33713a3f90c6SAndrew Thompson 		return;
3372902514f6SHans Petter Selasky 
3373f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
33743a3f90c6SAndrew Thompson 
33753a3f90c6SAndrew Thompson 	nchan = (d->bLength - 7) / d->bControlSize;
33763a3f90c6SAndrew Thompson 	mmask = uaudio_mixer_feature_get_bmaControls(d, 0);
33773a3f90c6SAndrew Thompson 	cmask = 0;
33783a3f90c6SAndrew Thompson 
3379902514f6SHans Petter Selasky 	if (nchan == 0)
33803a3f90c6SAndrew Thompson 		return;
3381902514f6SHans Petter Selasky 
33823a3f90c6SAndrew Thompson 	/* figure out what we can control */
33833a3f90c6SAndrew Thompson 
33843a3f90c6SAndrew Thompson 	for (chan = 1; chan < nchan; chan++) {
33853a3f90c6SAndrew Thompson 		DPRINTFN(10, "chan=%d mask=%x\n",
33863a3f90c6SAndrew Thompson 		    chan, uaudio_mixer_feature_get_bmaControls(d, chan));
33873a3f90c6SAndrew Thompson 
33883a3f90c6SAndrew Thompson 		cmask |= uaudio_mixer_feature_get_bmaControls(d, chan);
33893a3f90c6SAndrew Thompson 	}
33903a3f90c6SAndrew Thompson 
33913a3f90c6SAndrew Thompson 	if (nchan > MIX_MAX_CHAN) {
33923a3f90c6SAndrew Thompson 		nchan = MIX_MAX_CHAN;
33933a3f90c6SAndrew Thompson 	}
3394f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no);
33953a3f90c6SAndrew Thompson 
3396902514f6SHans Petter Selasky 	i = d->bmaControls[d->bControlSize];
3397902514f6SHans Petter Selasky 	if (i == 0 ||
3398902514f6SHans Petter Selasky 	    usbd_req_get_string_any(sc->sc_udev, NULL,
3399f7e62ad0SHans Petter Selasky 	    MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) {
3400f7e62ad0SHans Petter Selasky 		MIX(sc).desc[0] = 0;
3401902514f6SHans Petter Selasky 	}
3402902514f6SHans Petter Selasky 
34033a3f90c6SAndrew Thompson 	for (ctl = 1; ctl <= LOUDNESS_CONTROL; ctl++) {
34043a3f90c6SAndrew Thompson 
34053a3f90c6SAndrew Thompson 		fumask = FU_MASK(ctl);
34063a3f90c6SAndrew Thompson 
34073a3f90c6SAndrew Thompson 		DPRINTFN(5, "ctl=%d fumask=0x%04x\n",
34083a3f90c6SAndrew Thompson 		    ctl, fumask);
34093a3f90c6SAndrew Thompson 
34103a3f90c6SAndrew Thompson 		if (mmask & fumask) {
3411f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = 1;
3412f7e62ad0SHans Petter Selasky 			MIX(sc).wValue[0] = MAKE_WORD(ctl, 0);
34133a3f90c6SAndrew Thompson 		} else if (cmask & fumask) {
3414f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = nchan - 1;
34153a3f90c6SAndrew Thompson 			for (i = 1; i < nchan; i++) {
34163a3f90c6SAndrew Thompson 				if (uaudio_mixer_feature_get_bmaControls(d, i) & fumask)
3417f7e62ad0SHans Petter Selasky 					MIX(sc).wValue[i - 1] = MAKE_WORD(ctl, i);
34183a3f90c6SAndrew Thompson 				else
3419f7e62ad0SHans Petter Selasky 					MIX(sc).wValue[i - 1] = -1;
34203a3f90c6SAndrew Thompson 			}
34213a3f90c6SAndrew Thompson 		} else {
34223a3f90c6SAndrew Thompson 			continue;
34233a3f90c6SAndrew Thompson 		}
34243a3f90c6SAndrew Thompson 
3425f7e62ad0SHans Petter Selasky 		mixernumber = uaudio_mixer_feature_name(&iot[id], &MIX(sc));
34263a3f90c6SAndrew Thompson 
34273a3f90c6SAndrew Thompson 		switch (ctl) {
34283a3f90c6SAndrew Thompson 		case MUTE_CONTROL:
3429f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
3430f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
3431f7e62ad0SHans Petter Selasky 			MIX(sc).name = "mute";
34323a3f90c6SAndrew Thompson 			break;
34333a3f90c6SAndrew Thompson 
34343a3f90c6SAndrew Thompson 		case VOLUME_CONTROL:
3435f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_16;
3436f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = mixernumber;
3437f7e62ad0SHans Petter Selasky 			MIX(sc).name = "vol";
34383a3f90c6SAndrew Thompson 			break;
34393a3f90c6SAndrew Thompson 
34403a3f90c6SAndrew Thompson 		case BASS_CONTROL:
3441f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_8;
3442f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_BASS;
3443f7e62ad0SHans Petter Selasky 			MIX(sc).name = "bass";
34443a3f90c6SAndrew Thompson 			break;
34453a3f90c6SAndrew Thompson 
34463a3f90c6SAndrew Thompson 		case MID_CONTROL:
3447f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_8;
3448f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3449f7e62ad0SHans Petter Selasky 			MIX(sc).name = "mid";
34503a3f90c6SAndrew Thompson 			break;
34513a3f90c6SAndrew Thompson 
34523a3f90c6SAndrew Thompson 		case TREBLE_CONTROL:
3453f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_8;
3454f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_TREBLE;
3455f7e62ad0SHans Petter Selasky 			MIX(sc).name = "treble";
34563a3f90c6SAndrew Thompson 			break;
34573a3f90c6SAndrew Thompson 
34583a3f90c6SAndrew Thompson 		case GRAPHIC_EQUALIZER_CONTROL:
34593a3f90c6SAndrew Thompson 			continue;	/* XXX don't add anything */
34603a3f90c6SAndrew Thompson 
34613a3f90c6SAndrew Thompson 		case AGC_CONTROL:
3462f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
3463f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3464f7e62ad0SHans Petter Selasky 			MIX(sc).name = "agc";
34653a3f90c6SAndrew Thompson 			break;
34663a3f90c6SAndrew Thompson 
34673a3f90c6SAndrew Thompson 		case DELAY_CONTROL:
3468f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_UNSIGNED_16;
3469f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3470f7e62ad0SHans Petter Selasky 			MIX(sc).name = "delay";
34713a3f90c6SAndrew Thompson 			break;
34723a3f90c6SAndrew Thompson 
34733a3f90c6SAndrew Thompson 		case BASS_BOOST_CONTROL:
3474f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
3475f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3476f7e62ad0SHans Petter Selasky 			MIX(sc).name = "boost";
34773a3f90c6SAndrew Thompson 			break;
34783a3f90c6SAndrew Thompson 
34793a3f90c6SAndrew Thompson 		case LOUDNESS_CONTROL:
3480f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
3481f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_LOUD;	/* Is this correct ? */
3482f7e62ad0SHans Petter Selasky 			MIX(sc).name = "loudness";
34833a3f90c6SAndrew Thompson 			break;
34843a3f90c6SAndrew Thompson 
34853a3f90c6SAndrew Thompson 		default:
3486f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_UNKNOWN;
34873a3f90c6SAndrew Thompson 			break;
34883a3f90c6SAndrew Thompson 		}
34893a3f90c6SAndrew Thompson 
3490f7e62ad0SHans Petter Selasky 		if (MIX(sc).type != MIX_UNKNOWN)
3491f7e62ad0SHans Petter Selasky 			uaudio_mixer_add_ctl(sc, &MIX(sc));
34923a3f90c6SAndrew Thompson 	}
34933a3f90c6SAndrew Thompson }
3494e2524b2eSHans Petter Selasky 
3495e2524b2eSHans Petter Selasky static void
3496e2524b2eSHans Petter Selasky uaudio20_mixer_add_feature(struct uaudio_softc *sc,
3497e2524b2eSHans Petter Selasky     const struct uaudio_terminal_node *iot, int id)
3498e2524b2eSHans Petter Selasky {
3499e2524b2eSHans Petter Selasky 	const struct usb_audio20_feature_unit *d = iot[id].u.fu_v2;
3500e2524b2eSHans Petter Selasky 	uint32_t ctl;
3501e2524b2eSHans Petter Selasky 	uint32_t mmask;
3502e2524b2eSHans Petter Selasky 	uint32_t cmask;
3503e2524b2eSHans Petter Selasky 	uint16_t mixernumber;
3504e2524b2eSHans Petter Selasky 	uint8_t nchan;
3505e2524b2eSHans Petter Selasky 	uint8_t chan;
3506e2524b2eSHans Petter Selasky 	uint8_t i;
3507e2524b2eSHans Petter Selasky 	uint8_t what;
3508e2524b2eSHans Petter Selasky 
3509e2524b2eSHans Petter Selasky 	if (UGETDW(d->bmaControls[0]) == 0)
3510e2524b2eSHans Petter Selasky 		return;
3511e2524b2eSHans Petter Selasky 
3512f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
3513e2524b2eSHans Petter Selasky 
3514e2524b2eSHans Petter Selasky 	nchan = (d->bLength - 6) / 4;
3515e2524b2eSHans Petter Selasky 	mmask = UGETDW(d->bmaControls[0]);
3516e2524b2eSHans Petter Selasky 	cmask = 0;
3517e2524b2eSHans Petter Selasky 
3518e2524b2eSHans Petter Selasky 	if (nchan == 0)
3519e2524b2eSHans Petter Selasky 		return;
3520e2524b2eSHans Petter Selasky 
3521e2524b2eSHans Petter Selasky 	/* figure out what we can control */
3522e2524b2eSHans Petter Selasky 
3523e2524b2eSHans Petter Selasky 	for (chan = 1; chan < nchan; chan++)
3524e2524b2eSHans Petter Selasky 		cmask |= UGETDW(d->bmaControls[chan]);
3525e2524b2eSHans Petter Selasky 
3526e2524b2eSHans Petter Selasky 	if (nchan > MIX_MAX_CHAN)
3527e2524b2eSHans Petter Selasky 		nchan = MIX_MAX_CHAN;
3528e2524b2eSHans Petter Selasky 
3529f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no);
3530e2524b2eSHans Petter Selasky 
3531902514f6SHans Petter Selasky 	i = d->bmaControls[nchan][0];
3532902514f6SHans Petter Selasky 	if (i == 0 ||
3533902514f6SHans Petter Selasky 	    usbd_req_get_string_any(sc->sc_udev, NULL,
3534f7e62ad0SHans Petter Selasky 	    MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) {
3535f7e62ad0SHans Petter Selasky 		MIX(sc).desc[0] = 0;
3536902514f6SHans Petter Selasky 	}
3537902514f6SHans Petter Selasky 
3538e2524b2eSHans Petter Selasky 	for (ctl = 3; ctl != 0; ctl <<= 2) {
3539e2524b2eSHans Petter Selasky 
3540f7e62ad0SHans Petter Selasky 		mixernumber = uaudio20_mixer_feature_name(&iot[id], &MIX(sc));
3541e2524b2eSHans Petter Selasky 
3542e2524b2eSHans Petter Selasky 		switch (ctl) {
3543e2524b2eSHans Petter Selasky 		case (3 << 0):
3544f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
3545f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
3546f7e62ad0SHans Petter Selasky 			MIX(sc).name = "mute";
3547e2524b2eSHans Petter Selasky 			what = MUTE_CONTROL;
3548e2524b2eSHans Petter Selasky 			break;
3549e2524b2eSHans Petter Selasky 		case (3 << 2):
3550f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_16;
3551f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = mixernumber;
3552f7e62ad0SHans Petter Selasky 			MIX(sc).name = "vol";
3553e2524b2eSHans Petter Selasky 			what = VOLUME_CONTROL;
3554e2524b2eSHans Petter Selasky 			break;
3555e2524b2eSHans Petter Selasky 		case (3 << 4):
3556f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_8;
3557f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_BASS;
3558f7e62ad0SHans Petter Selasky 			MIX(sc).name = "bass";
3559e2524b2eSHans Petter Selasky 			what = BASS_CONTROL;
3560e2524b2eSHans Petter Selasky 			break;
3561e2524b2eSHans Petter Selasky 		case (3 << 6):
3562f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_8;
3563f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3564f7e62ad0SHans Petter Selasky 			MIX(sc).name = "mid";
3565e2524b2eSHans Petter Selasky 			what = MID_CONTROL;
3566e2524b2eSHans Petter Selasky 			break;
3567e2524b2eSHans Petter Selasky 		case (3 << 8):
3568f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_8;
3569f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_TREBLE;
3570f7e62ad0SHans Petter Selasky 			MIX(sc).name = "treble";
3571e2524b2eSHans Petter Selasky 			what = TREBLE_CONTROL;
3572e2524b2eSHans Petter Selasky 			break;
3573e2524b2eSHans Petter Selasky 		case (3 << 12):
3574f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
3575f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3576f7e62ad0SHans Petter Selasky 			MIX(sc).name = "agc";
3577e2524b2eSHans Petter Selasky 			what = AGC_CONTROL;
3578e2524b2eSHans Petter Selasky 			break;
3579e2524b2eSHans Petter Selasky 		case (3 << 14):
3580f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_UNSIGNED_16;
3581f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3582f7e62ad0SHans Petter Selasky 			MIX(sc).name = "delay";
3583e2524b2eSHans Petter Selasky 			what = DELAY_CONTROL;
3584e2524b2eSHans Petter Selasky 			break;
3585e2524b2eSHans Petter Selasky 		case (3 << 16):
3586f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
3587f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3588f7e62ad0SHans Petter Selasky 			MIX(sc).name = "boost";
3589e2524b2eSHans Petter Selasky 			what = BASS_BOOST_CONTROL;
3590e2524b2eSHans Petter Selasky 			break;
3591e2524b2eSHans Petter Selasky 		case (3 << 18):
3592f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
3593f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_LOUD;	/* Is this correct ? */
3594f7e62ad0SHans Petter Selasky 			MIX(sc).name = "loudness";
3595e2524b2eSHans Petter Selasky 			what = LOUDNESS_CONTROL;
3596e2524b2eSHans Petter Selasky 			break;
3597e2524b2eSHans Petter Selasky 		case (3 << 20):
3598f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_16;
3599f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = mixernumber;
3600f7e62ad0SHans Petter Selasky 			MIX(sc).name = "igain";
3601e2524b2eSHans Petter Selasky 			what = INPUT_GAIN_CONTROL;
3602e2524b2eSHans Petter Selasky 			break;
3603e2524b2eSHans Petter Selasky 		case (3 << 22):
3604f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_16;
3605f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = mixernumber;
3606f7e62ad0SHans Petter Selasky 			MIX(sc).name = "igainpad";
3607e2524b2eSHans Petter Selasky 			what = INPUT_GAIN_PAD_CONTROL;
3608e2524b2eSHans Petter Selasky 			break;
3609e2524b2eSHans Petter Selasky 		default:
3610e2524b2eSHans Petter Selasky 			continue;
3611e2524b2eSHans Petter Selasky 		}
3612e2524b2eSHans Petter Selasky 
3613e2524b2eSHans Petter Selasky 		if ((mmask & ctl) == ctl) {
3614f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = 1;
3615f7e62ad0SHans Petter Selasky 			MIX(sc).wValue[0] = MAKE_WORD(what, 0);
3616e2524b2eSHans Petter Selasky 		} else if ((cmask & ctl) == ctl) {
3617f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = nchan - 1;
3618e2524b2eSHans Petter Selasky 			for (i = 1; i < nchan; i++) {
3619e2524b2eSHans Petter Selasky 				if ((UGETDW(d->bmaControls[i]) & ctl) == ctl)
3620f7e62ad0SHans Petter Selasky 					MIX(sc).wValue[i - 1] = MAKE_WORD(what, i);
3621e2524b2eSHans Petter Selasky 				else
3622f7e62ad0SHans Petter Selasky 					MIX(sc).wValue[i - 1] = -1;
3623e2524b2eSHans Petter Selasky 			}
3624e2524b2eSHans Petter Selasky 		} else {
3625e2524b2eSHans Petter Selasky 			continue;
3626e2524b2eSHans Petter Selasky 		}
3627e2524b2eSHans Petter Selasky 
3628f7e62ad0SHans Petter Selasky 		if (MIX(sc).type != MIX_UNKNOWN)
3629f7e62ad0SHans Petter Selasky 			uaudio_mixer_add_ctl(sc, &MIX(sc));
3630e2524b2eSHans Petter Selasky 	}
36313a3f90c6SAndrew Thompson }
36323a3f90c6SAndrew Thompson 
36333a3f90c6SAndrew Thompson static void
36343a3f90c6SAndrew Thompson uaudio_mixer_add_processing_updown(struct uaudio_softc *sc,
36353a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
36363a3f90c6SAndrew Thompson {
3637e2524b2eSHans Petter Selasky 	const struct usb_audio_processing_unit_0 *d0 = iot[id].u.pu_v1;
36384c21be9bSRebecca Cran 	const struct usb_audio_processing_unit_1 *d1 =
36393a3f90c6SAndrew Thompson 	    (const void *)(d0->baSourceId + d0->bNrInPins);
36404c21be9bSRebecca Cran 	const struct usb_audio_processing_unit_updown *ud =
36413a3f90c6SAndrew Thompson 	    (const void *)(d1->bmControls + d1->bControlSize);
36423a3f90c6SAndrew Thompson 	uint8_t i;
36433a3f90c6SAndrew Thompson 
36443a3f90c6SAndrew Thompson 	if (uaudio_mixer_verify_desc(d0, sizeof(*ud)) == NULL) {
36453a3f90c6SAndrew Thompson 		return;
36463a3f90c6SAndrew Thompson 	}
36473a3f90c6SAndrew Thompson 	if (uaudio_mixer_verify_desc(d0, sizeof(*ud) + (2 * ud->bNrModes))
36483a3f90c6SAndrew Thompson 	    == NULL) {
36493a3f90c6SAndrew Thompson 		return;
36503a3f90c6SAndrew Thompson 	}
36513a3f90c6SAndrew Thompson 	DPRINTFN(3, "bUnitId=%d bNrModes=%d\n",
36523a3f90c6SAndrew Thompson 	    d0->bUnitId, ud->bNrModes);
36533a3f90c6SAndrew Thompson 
36543a3f90c6SAndrew Thompson 	if (!(d1->bmControls[0] & UA_PROC_MASK(UD_MODE_SELECT_CONTROL))) {
36553a3f90c6SAndrew Thompson 		DPRINTF("no mode select\n");
36563a3f90c6SAndrew Thompson 		return;
36573a3f90c6SAndrew Thompson 	}
3658f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
36593a3f90c6SAndrew Thompson 
3660f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no);
3661f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
3662f7e62ad0SHans Petter Selasky 	MIX(sc).wValue[0] = MAKE_WORD(UD_MODE_SELECT_CONTROL, 0);
3663f7e62ad0SHans Petter Selasky 	uaudio_mixer_determine_class(&iot[id], &MIX(sc));
3664f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_ON_OFF;		/* XXX */
36653a3f90c6SAndrew Thompson 
36663a3f90c6SAndrew Thompson 	for (i = 0; i < ud->bNrModes; i++) {
36673a3f90c6SAndrew Thompson 		DPRINTFN(3, "i=%d bm=0x%x\n", i, UGETW(ud->waModes[i]));
36683a3f90c6SAndrew Thompson 		/* XXX */
36693a3f90c6SAndrew Thompson 	}
36703a3f90c6SAndrew Thompson 
3671f7e62ad0SHans Petter Selasky 	uaudio_mixer_add_ctl(sc, &MIX(sc));
36723a3f90c6SAndrew Thompson }
36733a3f90c6SAndrew Thompson 
36743a3f90c6SAndrew Thompson static void
36753a3f90c6SAndrew Thompson uaudio_mixer_add_processing(struct uaudio_softc *sc,
36763a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
36773a3f90c6SAndrew Thompson {
3678e2524b2eSHans Petter Selasky 	const struct usb_audio_processing_unit_0 *d0 = iot[id].u.pu_v1;
36794c21be9bSRebecca Cran 	const struct usb_audio_processing_unit_1 *d1 =
36803a3f90c6SAndrew Thompson 	    (const void *)(d0->baSourceId + d0->bNrInPins);
36813a3f90c6SAndrew Thompson 	uint16_t ptype;
36823a3f90c6SAndrew Thompson 
3683f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
36843a3f90c6SAndrew Thompson 
36853a3f90c6SAndrew Thompson 	ptype = UGETW(d0->wProcessType);
36863a3f90c6SAndrew Thompson 
36873a3f90c6SAndrew Thompson 	DPRINTFN(3, "wProcessType=%d bUnitId=%d "
36883a3f90c6SAndrew Thompson 	    "bNrInPins=%d\n", ptype, d0->bUnitId, d0->bNrInPins);
36893a3f90c6SAndrew Thompson 
36903a3f90c6SAndrew Thompson 	if (d1->bControlSize == 0) {
36913a3f90c6SAndrew Thompson 		return;
36923a3f90c6SAndrew Thompson 	}
36933a3f90c6SAndrew Thompson 	if (d1->bmControls[0] & UA_PROC_ENABLE_MASK) {
3694f7e62ad0SHans Petter Selasky 		MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no);
3695f7e62ad0SHans Petter Selasky 		MIX(sc).nchan = 1;
3696f7e62ad0SHans Petter Selasky 		MIX(sc).wValue[0] = MAKE_WORD(XX_ENABLE_CONTROL, 0);
3697f7e62ad0SHans Petter Selasky 		uaudio_mixer_determine_class(&iot[id], &MIX(sc));
3698f7e62ad0SHans Petter Selasky 		MIX(sc).type = MIX_ON_OFF;
3699f7e62ad0SHans Petter Selasky 		uaudio_mixer_add_ctl(sc, &MIX(sc));
37003a3f90c6SAndrew Thompson 	}
37013a3f90c6SAndrew Thompson 	switch (ptype) {
37023a3f90c6SAndrew Thompson 	case UPDOWNMIX_PROCESS:
37033a3f90c6SAndrew Thompson 		uaudio_mixer_add_processing_updown(sc, iot, id);
37043a3f90c6SAndrew Thompson 		break;
37053a3f90c6SAndrew Thompson 
37063a3f90c6SAndrew Thompson 	case DOLBY_PROLOGIC_PROCESS:
37073a3f90c6SAndrew Thompson 	case P3D_STEREO_EXTENDER_PROCESS:
37083a3f90c6SAndrew Thompson 	case REVERBATION_PROCESS:
37093a3f90c6SAndrew Thompson 	case CHORUS_PROCESS:
37103a3f90c6SAndrew Thompson 	case DYN_RANGE_COMP_PROCESS:
37113a3f90c6SAndrew Thompson 	default:
37123a3f90c6SAndrew Thompson 		DPRINTF("unit %d, type=%d is not implemented\n",
37133a3f90c6SAndrew Thompson 		    d0->bUnitId, ptype);
37143a3f90c6SAndrew Thompson 		break;
37153a3f90c6SAndrew Thompson 	}
37163a3f90c6SAndrew Thompson }
37173a3f90c6SAndrew Thompson 
37183a3f90c6SAndrew Thompson static void
37193a3f90c6SAndrew Thompson uaudio_mixer_add_extension(struct uaudio_softc *sc,
37203a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
37213a3f90c6SAndrew Thompson {
3722e2524b2eSHans Petter Selasky 	const struct usb_audio_extension_unit_0 *d0 = iot[id].u.eu_v1;
37234c21be9bSRebecca Cran 	const struct usb_audio_extension_unit_1 *d1 =
37243a3f90c6SAndrew Thompson 	    (const void *)(d0->baSourceId + d0->bNrInPins);
37253a3f90c6SAndrew Thompson 
37263a3f90c6SAndrew Thompson 	DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n",
37273a3f90c6SAndrew Thompson 	    d0->bUnitId, d0->bNrInPins);
37283a3f90c6SAndrew Thompson 
37293a3f90c6SAndrew Thompson 	if (sc->sc_uq_au_no_xu) {
37303a3f90c6SAndrew Thompson 		return;
37313a3f90c6SAndrew Thompson 	}
37323a3f90c6SAndrew Thompson 	if (d1->bControlSize == 0) {
37333a3f90c6SAndrew Thompson 		return;
37343a3f90c6SAndrew Thompson 	}
37353a3f90c6SAndrew Thompson 	if (d1->bmControls[0] & UA_EXT_ENABLE_MASK) {
37363a3f90c6SAndrew Thompson 
3737f7e62ad0SHans Petter Selasky 		memset(&MIX(sc), 0, sizeof(MIX(sc)));
37383a3f90c6SAndrew Thompson 
3739f7e62ad0SHans Petter Selasky 		MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no);
3740f7e62ad0SHans Petter Selasky 		MIX(sc).nchan = 1;
3741f7e62ad0SHans Petter Selasky 		MIX(sc).wValue[0] = MAKE_WORD(UA_EXT_ENABLE, 0);
3742f7e62ad0SHans Petter Selasky 		uaudio_mixer_determine_class(&iot[id], &MIX(sc));
3743f7e62ad0SHans Petter Selasky 		MIX(sc).type = MIX_ON_OFF;
37443a3f90c6SAndrew Thompson 
3745f7e62ad0SHans Petter Selasky 		uaudio_mixer_add_ctl(sc, &MIX(sc));
37463a3f90c6SAndrew Thompson 	}
37473a3f90c6SAndrew Thompson }
37483a3f90c6SAndrew Thompson 
37493a3f90c6SAndrew Thompson static const void *
37503a3f90c6SAndrew Thompson uaudio_mixer_verify_desc(const void *arg, uint32_t len)
37513a3f90c6SAndrew Thompson {
37524c21be9bSRebecca Cran 	const struct usb_audio_mixer_unit_1 *d1;
37534c21be9bSRebecca Cran 	const struct usb_audio_extension_unit_1 *e1;
37544c21be9bSRebecca Cran 	const struct usb_audio_processing_unit_1 *u1;
37553a3f90c6SAndrew Thompson 
37563a3f90c6SAndrew Thompson 	union {
3757760bc48eSAndrew Thompson 		const struct usb_descriptor *desc;
37584c21be9bSRebecca Cran 		const struct usb_audio_input_terminal *it;
37594c21be9bSRebecca Cran 		const struct usb_audio_output_terminal *ot;
37604c21be9bSRebecca Cran 		const struct usb_audio_mixer_unit_0 *mu;
37614c21be9bSRebecca Cran 		const struct usb_audio_selector_unit *su;
37624c21be9bSRebecca Cran 		const struct usb_audio_feature_unit *fu;
37634c21be9bSRebecca Cran 		const struct usb_audio_processing_unit_0 *pu;
37644c21be9bSRebecca Cran 		const struct usb_audio_extension_unit_0 *eu;
37653a3f90c6SAndrew Thompson 	}     u;
37663a3f90c6SAndrew Thompson 
37673a3f90c6SAndrew Thompson 	u.desc = arg;
37683a3f90c6SAndrew Thompson 
37693a3f90c6SAndrew Thompson 	if (u.desc == NULL) {
37703a3f90c6SAndrew Thompson 		goto error;
37713a3f90c6SAndrew Thompson 	}
37723a3f90c6SAndrew Thompson 	if (u.desc->bDescriptorType != UDESC_CS_INTERFACE) {
37733a3f90c6SAndrew Thompson 		goto error;
37743a3f90c6SAndrew Thompson 	}
37753a3f90c6SAndrew Thompson 	switch (u.desc->bDescriptorSubtype) {
37763a3f90c6SAndrew Thompson 	case UDESCSUB_AC_INPUT:
37773a3f90c6SAndrew Thompson 		len += sizeof(*u.it);
37783a3f90c6SAndrew Thompson 		break;
37793a3f90c6SAndrew Thompson 
37803a3f90c6SAndrew Thompson 	case UDESCSUB_AC_OUTPUT:
37813a3f90c6SAndrew Thompson 		len += sizeof(*u.ot);
37823a3f90c6SAndrew Thompson 		break;
37833a3f90c6SAndrew Thompson 
37843a3f90c6SAndrew Thompson 	case UDESCSUB_AC_MIXER:
37853a3f90c6SAndrew Thompson 		len += sizeof(*u.mu);
37863a3f90c6SAndrew Thompson 
37873a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
37883a3f90c6SAndrew Thompson 			goto error;
37893a3f90c6SAndrew Thompson 		}
37903a3f90c6SAndrew Thompson 		len += u.mu->bNrInPins;
37913a3f90c6SAndrew Thompson 
37923a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
37933a3f90c6SAndrew Thompson 			goto error;
37943a3f90c6SAndrew Thompson 		}
37953a3f90c6SAndrew Thompson 		d1 = (const void *)(u.mu->baSourceId + u.mu->bNrInPins);
37963a3f90c6SAndrew Thompson 
37973a3f90c6SAndrew Thompson 		len += sizeof(*d1);
37983a3f90c6SAndrew Thompson 		break;
37993a3f90c6SAndrew Thompson 
38003a3f90c6SAndrew Thompson 	case UDESCSUB_AC_SELECTOR:
38013a3f90c6SAndrew Thompson 		len += sizeof(*u.su);
38023a3f90c6SAndrew Thompson 
38033a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
38043a3f90c6SAndrew Thompson 			goto error;
38053a3f90c6SAndrew Thompson 		}
3806902514f6SHans Petter Selasky 		len += u.su->bNrInPins + 1;
38073a3f90c6SAndrew Thompson 		break;
38083a3f90c6SAndrew Thompson 
38093a3f90c6SAndrew Thompson 	case UDESCSUB_AC_FEATURE:
3810902514f6SHans Petter Selasky 		len += sizeof(*u.fu) + 1;
3811902514f6SHans Petter Selasky 
3812902514f6SHans Petter Selasky 		if (u.desc->bLength < len)
3813902514f6SHans Petter Selasky 			goto error;
3814902514f6SHans Petter Selasky 
3815902514f6SHans Petter Selasky 		len += u.fu->bControlSize;
38163a3f90c6SAndrew Thompson 		break;
38173a3f90c6SAndrew Thompson 
38183a3f90c6SAndrew Thompson 	case UDESCSUB_AC_PROCESSING:
38193a3f90c6SAndrew Thompson 		len += sizeof(*u.pu);
38203a3f90c6SAndrew Thompson 
38213a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
38223a3f90c6SAndrew Thompson 			goto error;
38233a3f90c6SAndrew Thompson 		}
38243a3f90c6SAndrew Thompson 		len += u.pu->bNrInPins;
38253a3f90c6SAndrew Thompson 
38263a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
38273a3f90c6SAndrew Thompson 			goto error;
38283a3f90c6SAndrew Thompson 		}
38293a3f90c6SAndrew Thompson 		u1 = (const void *)(u.pu->baSourceId + u.pu->bNrInPins);
38303a3f90c6SAndrew Thompson 
38313a3f90c6SAndrew Thompson 		len += sizeof(*u1);
38323a3f90c6SAndrew Thompson 
38333a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
38343a3f90c6SAndrew Thompson 			goto error;
38353a3f90c6SAndrew Thompson 		}
38363a3f90c6SAndrew Thompson 		len += u1->bControlSize;
38373a3f90c6SAndrew Thompson 
38383a3f90c6SAndrew Thompson 		break;
38393a3f90c6SAndrew Thompson 
38403a3f90c6SAndrew Thompson 	case UDESCSUB_AC_EXTENSION:
38413a3f90c6SAndrew Thompson 		len += sizeof(*u.eu);
38423a3f90c6SAndrew Thompson 
38433a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
38443a3f90c6SAndrew Thompson 			goto error;
38453a3f90c6SAndrew Thompson 		}
38463a3f90c6SAndrew Thompson 		len += u.eu->bNrInPins;
38473a3f90c6SAndrew Thompson 
38483a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
38493a3f90c6SAndrew Thompson 			goto error;
38503a3f90c6SAndrew Thompson 		}
38513a3f90c6SAndrew Thompson 		e1 = (const void *)(u.eu->baSourceId + u.eu->bNrInPins);
38523a3f90c6SAndrew Thompson 
38533a3f90c6SAndrew Thompson 		len += sizeof(*e1);
38543a3f90c6SAndrew Thompson 
38553a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
38563a3f90c6SAndrew Thompson 			goto error;
38573a3f90c6SAndrew Thompson 		}
38583a3f90c6SAndrew Thompson 		len += e1->bControlSize;
38593a3f90c6SAndrew Thompson 		break;
38603a3f90c6SAndrew Thompson 
38613a3f90c6SAndrew Thompson 	default:
38623a3f90c6SAndrew Thompson 		goto error;
38633a3f90c6SAndrew Thompson 	}
38643a3f90c6SAndrew Thompson 
38653a3f90c6SAndrew Thompson 	if (u.desc->bLength < len) {
38663a3f90c6SAndrew Thompson 		goto error;
38673a3f90c6SAndrew Thompson 	}
38683a3f90c6SAndrew Thompson 	return (u.desc);
38693a3f90c6SAndrew Thompson 
38703a3f90c6SAndrew Thompson error:
38713a3f90c6SAndrew Thompson 	if (u.desc) {
38723a3f90c6SAndrew Thompson 		DPRINTF("invalid descriptor, type=%d, "
38733a3f90c6SAndrew Thompson 		    "sub_type=%d, len=%d of %d bytes\n",
38743a3f90c6SAndrew Thompson 		    u.desc->bDescriptorType,
38753a3f90c6SAndrew Thompson 		    u.desc->bDescriptorSubtype,
38763a3f90c6SAndrew Thompson 		    u.desc->bLength, len);
38773a3f90c6SAndrew Thompson 	}
38783a3f90c6SAndrew Thompson 	return (NULL);
38793a3f90c6SAndrew Thompson }
38803a3f90c6SAndrew Thompson 
3881e2524b2eSHans Petter Selasky static const void *
3882e2524b2eSHans Petter Selasky uaudio20_mixer_verify_desc(const void *arg, uint32_t len)
38833a3f90c6SAndrew Thompson {
3884e2524b2eSHans Petter Selasky 	const struct usb_audio20_mixer_unit_1 *d1;
3885e2524b2eSHans Petter Selasky 	const struct usb_audio20_extension_unit_1 *e1;
3886e2524b2eSHans Petter Selasky 	const struct usb_audio20_processing_unit_1 *u1;
3887e2524b2eSHans Petter Selasky 	const struct usb_audio20_clock_selector_unit_1 *c1;
38883a3f90c6SAndrew Thompson 
3889e2524b2eSHans Petter Selasky 	union {
3890e2524b2eSHans Petter Selasky 		const struct usb_descriptor *desc;
3891e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_source_unit *csrc;
3892e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_selector_unit_0 *csel;
3893e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_multiplier_unit *cmul;
3894e2524b2eSHans Petter Selasky 		const struct usb_audio20_input_terminal *it;
3895e2524b2eSHans Petter Selasky 		const struct usb_audio20_output_terminal *ot;
3896e2524b2eSHans Petter Selasky 		const struct usb_audio20_mixer_unit_0 *mu;
3897e2524b2eSHans Petter Selasky 		const struct usb_audio20_selector_unit *su;
3898e2524b2eSHans Petter Selasky 		const struct usb_audio20_feature_unit *fu;
3899e2524b2eSHans Petter Selasky 		const struct usb_audio20_sample_rate_unit *ru;
3900e2524b2eSHans Petter Selasky 		const struct usb_audio20_processing_unit_0 *pu;
3901e2524b2eSHans Petter Selasky 		const struct usb_audio20_extension_unit_0 *eu;
3902e2524b2eSHans Petter Selasky 		const struct usb_audio20_effect_unit *ef;
3903e2524b2eSHans Petter Selasky 	}     u;
39043a3f90c6SAndrew Thompson 
3905e2524b2eSHans Petter Selasky 	u.desc = arg;
39063a3f90c6SAndrew Thompson 
3907e2524b2eSHans Petter Selasky 	if (u.desc == NULL)
3908e2524b2eSHans Petter Selasky 		goto error;
3909e2524b2eSHans Petter Selasky 
3910e2524b2eSHans Petter Selasky 	if (u.desc->bDescriptorType != UDESC_CS_INTERFACE)
3911e2524b2eSHans Petter Selasky 		goto error;
3912e2524b2eSHans Petter Selasky 
3913e2524b2eSHans Petter Selasky 	switch (u.desc->bDescriptorSubtype) {
3914e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_INPUT:
3915e2524b2eSHans Petter Selasky 		len += sizeof(*u.it);
3916e2524b2eSHans Petter Selasky 		break;
3917e2524b2eSHans Petter Selasky 
3918e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_OUTPUT:
3919e2524b2eSHans Petter Selasky 		len += sizeof(*u.ot);
3920e2524b2eSHans Petter Selasky 		break;
3921e2524b2eSHans Petter Selasky 
3922e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_MIXER:
3923e2524b2eSHans Petter Selasky 		len += sizeof(*u.mu);
3924e2524b2eSHans Petter Selasky 
3925e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
3926e2524b2eSHans Petter Selasky 			goto error;
3927e2524b2eSHans Petter Selasky 		len += u.mu->bNrInPins;
3928e2524b2eSHans Petter Selasky 
3929e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
3930e2524b2eSHans Petter Selasky 			goto error;
3931e2524b2eSHans Petter Selasky 
3932e2524b2eSHans Petter Selasky 		d1 = (const void *)(u.mu->baSourceId + u.mu->bNrInPins);
3933e2524b2eSHans Petter Selasky 
3934e2524b2eSHans Petter Selasky 		len += sizeof(*d1) + d1->bNrChannels;
3935e2524b2eSHans Petter Selasky 		break;
3936e2524b2eSHans Petter Selasky 
3937e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_SELECTOR:
3938e2524b2eSHans Petter Selasky 		len += sizeof(*u.su);
3939e2524b2eSHans Petter Selasky 
3940e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
3941e2524b2eSHans Petter Selasky 			goto error;
3942e2524b2eSHans Petter Selasky 
3943902514f6SHans Petter Selasky 		len += u.su->bNrInPins + 1;
3944e2524b2eSHans Petter Selasky 		break;
3945e2524b2eSHans Petter Selasky 
3946e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_FEATURE:
3947e2524b2eSHans Petter Selasky 		len += sizeof(*u.fu) + 1;
3948e2524b2eSHans Petter Selasky 		break;
3949e2524b2eSHans Petter Selasky 
3950e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_EFFECT:
3951e2524b2eSHans Petter Selasky 		len += sizeof(*u.ef) + 4;
3952e2524b2eSHans Petter Selasky 		break;
3953e2524b2eSHans Petter Selasky 
3954e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_PROCESSING_V2:
3955e2524b2eSHans Petter Selasky 		len += sizeof(*u.pu);
3956e2524b2eSHans Petter Selasky 
3957e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
3958e2524b2eSHans Petter Selasky 			goto error;
3959e2524b2eSHans Petter Selasky 
3960e2524b2eSHans Petter Selasky 		len += u.pu->bNrInPins;
3961e2524b2eSHans Petter Selasky 
3962e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
3963e2524b2eSHans Petter Selasky 			goto error;
3964e2524b2eSHans Petter Selasky 
3965e2524b2eSHans Petter Selasky 		u1 = (const void *)(u.pu->baSourceId + u.pu->bNrInPins);
3966e2524b2eSHans Petter Selasky 
3967e2524b2eSHans Petter Selasky 		len += sizeof(*u1);
3968e2524b2eSHans Petter Selasky 		break;
3969e2524b2eSHans Petter Selasky 
3970e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_EXTENSION_V2:
3971e2524b2eSHans Petter Selasky 		len += sizeof(*u.eu);
3972e2524b2eSHans Petter Selasky 
3973e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
3974e2524b2eSHans Petter Selasky 			goto error;
3975e2524b2eSHans Petter Selasky 
3976e2524b2eSHans Petter Selasky 		len += u.eu->bNrInPins;
3977e2524b2eSHans Petter Selasky 
3978e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
3979e2524b2eSHans Petter Selasky 			goto error;
3980e2524b2eSHans Petter Selasky 
3981e2524b2eSHans Petter Selasky 		e1 = (const void *)(u.eu->baSourceId + u.eu->bNrInPins);
3982e2524b2eSHans Petter Selasky 
3983e2524b2eSHans Petter Selasky 		len += sizeof(*e1);
3984e2524b2eSHans Petter Selasky 		break;
3985e2524b2eSHans Petter Selasky 
3986e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_CLOCK_SRC:
3987e2524b2eSHans Petter Selasky 		len += sizeof(*u.csrc);
3988e2524b2eSHans Petter Selasky 		break;
3989e2524b2eSHans Petter Selasky 
3990e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_CLOCK_SEL:
3991e2524b2eSHans Petter Selasky 		len += sizeof(*u.csel);
3992e2524b2eSHans Petter Selasky 
3993e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
3994e2524b2eSHans Petter Selasky 			goto error;
3995e2524b2eSHans Petter Selasky 
3996e2524b2eSHans Petter Selasky 		len += u.csel->bNrInPins;
3997e2524b2eSHans Petter Selasky 
3998e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
3999e2524b2eSHans Petter Selasky 			goto error;
4000e2524b2eSHans Petter Selasky 
4001e2524b2eSHans Petter Selasky 		c1 = (const void *)(u.csel->baCSourceId + u.csel->bNrInPins);
4002e2524b2eSHans Petter Selasky 
4003e2524b2eSHans Petter Selasky 		len += sizeof(*c1);
4004e2524b2eSHans Petter Selasky 		break;
4005e2524b2eSHans Petter Selasky 
4006e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_CLOCK_MUL:
4007e2524b2eSHans Petter Selasky 		len += sizeof(*u.cmul);
4008e2524b2eSHans Petter Selasky 		break;
4009e2524b2eSHans Petter Selasky 
4010e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_SAMPLE_RT:
4011e2524b2eSHans Petter Selasky 		len += sizeof(*u.ru);
4012e2524b2eSHans Petter Selasky 		break;
4013e2524b2eSHans Petter Selasky 
4014e2524b2eSHans Petter Selasky 	default:
4015e2524b2eSHans Petter Selasky 		goto error;
40163a3f90c6SAndrew Thompson 	}
40173a3f90c6SAndrew Thompson 
4018e2524b2eSHans Petter Selasky 	if (u.desc->bLength < len)
4019e2524b2eSHans Petter Selasky 		goto error;
4020e2524b2eSHans Petter Selasky 
4021e2524b2eSHans Petter Selasky 	return (u.desc);
4022e2524b2eSHans Petter Selasky 
4023e2524b2eSHans Petter Selasky error:
4024e2524b2eSHans Petter Selasky 	if (u.desc) {
4025e2524b2eSHans Petter Selasky 		DPRINTF("invalid descriptor, type=%d, "
4026e2524b2eSHans Petter Selasky 		    "sub_type=%d, len=%d of %d bytes\n",
4027e2524b2eSHans Petter Selasky 		    u.desc->bDescriptorType,
4028e2524b2eSHans Petter Selasky 		    u.desc->bDescriptorSubtype,
4029e2524b2eSHans Petter Selasky 		    u.desc->bLength, len);
4030e2524b2eSHans Petter Selasky 	}
4031e2524b2eSHans Petter Selasky 	return (NULL);
4032e2524b2eSHans Petter Selasky }
40333a3f90c6SAndrew Thompson 
40344c21be9bSRebecca Cran static struct usb_audio_cluster
40353a3f90c6SAndrew Thompson uaudio_mixer_get_cluster(uint8_t id, const struct uaudio_terminal_node *iot)
40363a3f90c6SAndrew Thompson {
40374c21be9bSRebecca Cran 	struct usb_audio_cluster r;
4038760bc48eSAndrew Thompson 	const struct usb_descriptor *dp;
40393a3f90c6SAndrew Thompson 	uint8_t i;
40403a3f90c6SAndrew Thompson 
40413a3f90c6SAndrew Thompson 	for (i = 0; i < UAUDIO_RECURSE_LIMIT; i++) {	/* avoid infinite loops */
40423a3f90c6SAndrew Thompson 		dp = iot[id].u.desc;
40433a3f90c6SAndrew Thompson 		if (dp == NULL) {
40443a3f90c6SAndrew Thompson 			goto error;
40453a3f90c6SAndrew Thompson 		}
40463a3f90c6SAndrew Thompson 		switch (dp->bDescriptorSubtype) {
40473a3f90c6SAndrew Thompson 		case UDESCSUB_AC_INPUT:
4048e2524b2eSHans Petter Selasky 			r.bNrChannels = iot[id].u.it_v1->bNrChannels;
4049e2524b2eSHans Petter Selasky 			r.wChannelConfig[0] = iot[id].u.it_v1->wChannelConfig[0];
4050e2524b2eSHans Petter Selasky 			r.wChannelConfig[1] = iot[id].u.it_v1->wChannelConfig[1];
4051e2524b2eSHans Petter Selasky 			r.iChannelNames = iot[id].u.it_v1->iChannelNames;
40523a3f90c6SAndrew Thompson 			goto done;
40533a3f90c6SAndrew Thompson 
40543a3f90c6SAndrew Thompson 		case UDESCSUB_AC_OUTPUT:
4055e2524b2eSHans Petter Selasky 			id = iot[id].u.ot_v1->bSourceId;
40563a3f90c6SAndrew Thompson 			break;
40573a3f90c6SAndrew Thompson 
40583a3f90c6SAndrew Thompson 		case UDESCSUB_AC_MIXER:
40594c21be9bSRebecca Cran 			r = *(const struct usb_audio_cluster *)
4060e2524b2eSHans Petter Selasky 			    &iot[id].u.mu_v1->baSourceId[
4061e2524b2eSHans Petter Selasky 			    iot[id].u.mu_v1->bNrInPins];
40623a3f90c6SAndrew Thompson 			goto done;
40633a3f90c6SAndrew Thompson 
40643a3f90c6SAndrew Thompson 		case UDESCSUB_AC_SELECTOR:
4065e2524b2eSHans Petter Selasky 			if (iot[id].u.su_v1->bNrInPins > 0) {
40663a3f90c6SAndrew Thompson 				/* XXX This is not really right */
4067e2524b2eSHans Petter Selasky 				id = iot[id].u.su_v1->baSourceId[0];
40683a3f90c6SAndrew Thompson 			}
40693a3f90c6SAndrew Thompson 			break;
40703a3f90c6SAndrew Thompson 
40713a3f90c6SAndrew Thompson 		case UDESCSUB_AC_FEATURE:
4072e2524b2eSHans Petter Selasky 			id = iot[id].u.fu_v1->bSourceId;
40733a3f90c6SAndrew Thompson 			break;
40743a3f90c6SAndrew Thompson 
40753a3f90c6SAndrew Thompson 		case UDESCSUB_AC_PROCESSING:
40764c21be9bSRebecca Cran 			r = *((const struct usb_audio_cluster *)
4077e2524b2eSHans Petter Selasky 			    &iot[id].u.pu_v1->baSourceId[
4078e2524b2eSHans Petter Selasky 			    iot[id].u.pu_v1->bNrInPins]);
40793a3f90c6SAndrew Thompson 			goto done;
40803a3f90c6SAndrew Thompson 
40813a3f90c6SAndrew Thompson 		case UDESCSUB_AC_EXTENSION:
40824c21be9bSRebecca Cran 			r = *((const struct usb_audio_cluster *)
4083e2524b2eSHans Petter Selasky 			    &iot[id].u.eu_v1->baSourceId[
4084e2524b2eSHans Petter Selasky 			    iot[id].u.eu_v1->bNrInPins]);
40853a3f90c6SAndrew Thompson 			goto done;
40863a3f90c6SAndrew Thompson 
40873a3f90c6SAndrew Thompson 		default:
40883a3f90c6SAndrew Thompson 			goto error;
40893a3f90c6SAndrew Thompson 		}
40903a3f90c6SAndrew Thompson 	}
40913a3f90c6SAndrew Thompson error:
40923a3f90c6SAndrew Thompson 	DPRINTF("bad data\n");
40936f068a43SHans Petter Selasky 	memset(&r, 0, sizeof(r));
40943a3f90c6SAndrew Thompson done:
40953a3f90c6SAndrew Thompson 	return (r);
40963a3f90c6SAndrew Thompson }
40973a3f90c6SAndrew Thompson 
4098e2524b2eSHans Petter Selasky static struct usb_audio20_cluster
4099e2524b2eSHans Petter Selasky uaudio20_mixer_get_cluster(uint8_t id, const struct uaudio_terminal_node *iot)
41003a3f90c6SAndrew Thompson {
4101e2524b2eSHans Petter Selasky 	struct usb_audio20_cluster r;
4102e2524b2eSHans Petter Selasky 	const struct usb_descriptor *dp;
4103e2524b2eSHans Petter Selasky 	uint8_t i;
41043a3f90c6SAndrew Thompson 
4105e2524b2eSHans Petter Selasky 	for (i = 0; i < UAUDIO_RECURSE_LIMIT; i++) {	/* avoid infinite loops */
4106e2524b2eSHans Petter Selasky 		dp = iot[id].u.desc;
4107e2524b2eSHans Petter Selasky 		if (dp == NULL)
4108e2524b2eSHans Petter Selasky 			goto error;
4109e2524b2eSHans Petter Selasky 
4110e2524b2eSHans Petter Selasky 		switch (dp->bDescriptorSubtype) {
4111e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_INPUT:
4112e2524b2eSHans Petter Selasky 			r.bNrChannels = iot[id].u.it_v2->bNrChannels;
4113e2524b2eSHans Petter Selasky 			r.bmChannelConfig[0] = iot[id].u.it_v2->bmChannelConfig[0];
4114e2524b2eSHans Petter Selasky 			r.bmChannelConfig[1] = iot[id].u.it_v2->bmChannelConfig[1];
4115e2524b2eSHans Petter Selasky 			r.bmChannelConfig[2] = iot[id].u.it_v2->bmChannelConfig[2];
4116e2524b2eSHans Petter Selasky 			r.bmChannelConfig[3] = iot[id].u.it_v2->bmChannelConfig[3];
4117e2524b2eSHans Petter Selasky 			r.iChannelNames = iot[id].u.it_v2->iTerminal;
4118e2524b2eSHans Petter Selasky 			goto done;
4119e2524b2eSHans Petter Selasky 
4120e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_OUTPUT:
4121e2524b2eSHans Petter Selasky 			id = iot[id].u.ot_v2->bSourceId;
41223a3f90c6SAndrew Thompson 			break;
41233a3f90c6SAndrew Thompson 
4124e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_MIXER:
4125e2524b2eSHans Petter Selasky 			r = *(const struct usb_audio20_cluster *)
4126e2524b2eSHans Petter Selasky 			    &iot[id].u.mu_v2->baSourceId[
4127e2524b2eSHans Petter Selasky 			    iot[id].u.mu_v2->bNrInPins];
4128e2524b2eSHans Petter Selasky 			goto done;
4129e2524b2eSHans Petter Selasky 
4130e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_SELECTOR:
4131e2524b2eSHans Petter Selasky 			if (iot[id].u.su_v2->bNrInPins > 0) {
4132e2524b2eSHans Petter Selasky 				/* XXX This is not really right */
4133e2524b2eSHans Petter Selasky 				id = iot[id].u.su_v2->baSourceId[0];
4134e2524b2eSHans Petter Selasky 			}
4135e2524b2eSHans Petter Selasky 			break;
4136e2524b2eSHans Petter Selasky 
4137e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_SAMPLE_RT:
4138e2524b2eSHans Petter Selasky 			id = iot[id].u.ru_v2->bSourceId;
4139e2524b2eSHans Petter Selasky 			break;
4140e2524b2eSHans Petter Selasky 
4141e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_EFFECT:
4142e2524b2eSHans Petter Selasky 			id = iot[id].u.ef_v2->bSourceId;
4143e2524b2eSHans Petter Selasky 			break;
4144e2524b2eSHans Petter Selasky 
4145e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_FEATURE:
4146e2524b2eSHans Petter Selasky 			id = iot[id].u.fu_v2->bSourceId;
4147e2524b2eSHans Petter Selasky 			break;
4148e2524b2eSHans Petter Selasky 
4149e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_PROCESSING_V2:
4150e2524b2eSHans Petter Selasky 			r = *((const struct usb_audio20_cluster *)
4151e2524b2eSHans Petter Selasky 			    &iot[id].u.pu_v2->baSourceId[
4152e2524b2eSHans Petter Selasky 			    iot[id].u.pu_v2->bNrInPins]);
4153e2524b2eSHans Petter Selasky 			goto done;
4154e2524b2eSHans Petter Selasky 
4155e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_EXTENSION_V2:
4156e2524b2eSHans Petter Selasky 			r = *((const struct usb_audio20_cluster *)
4157e2524b2eSHans Petter Selasky 			    &iot[id].u.eu_v2->baSourceId[
4158e2524b2eSHans Petter Selasky 			    iot[id].u.eu_v2->bNrInPins]);
4159e2524b2eSHans Petter Selasky 			goto done;
4160e2524b2eSHans Petter Selasky 
4161e2524b2eSHans Petter Selasky 		default:
4162e2524b2eSHans Petter Selasky 			goto error;
4163e2524b2eSHans Petter Selasky 		}
4164e2524b2eSHans Petter Selasky 	}
4165e2524b2eSHans Petter Selasky error:
4166e2524b2eSHans Petter Selasky 	DPRINTF("Bad data!\n");
4167e2524b2eSHans Petter Selasky 	memset(&r, 0, sizeof(r));
4168e2524b2eSHans Petter Selasky done:
4169e2524b2eSHans Petter Selasky 	return (r);
4170e2524b2eSHans Petter Selasky }
41713a3f90c6SAndrew Thompson 
41723a3f90c6SAndrew Thompson static uint16_t
41733a3f90c6SAndrew Thompson uaudio_mixer_determine_class(const struct uaudio_terminal_node *iot,
41743a3f90c6SAndrew Thompson     struct uaudio_mixer_node *mix)
41753a3f90c6SAndrew Thompson {
41763a3f90c6SAndrew Thompson 	uint16_t terminal_type = 0x0000;
41773a3f90c6SAndrew Thompson 	const struct uaudio_terminal_node *input[2];
41783a3f90c6SAndrew Thompson 	const struct uaudio_terminal_node *output[2];
41793a3f90c6SAndrew Thompson 
41803a3f90c6SAndrew Thompson 	input[0] = uaudio_mixer_get_input(iot, 0);
41813a3f90c6SAndrew Thompson 	input[1] = uaudio_mixer_get_input(iot, 1);
41823a3f90c6SAndrew Thompson 
41833a3f90c6SAndrew Thompson 	output[0] = uaudio_mixer_get_output(iot, 0);
41843a3f90c6SAndrew Thompson 	output[1] = uaudio_mixer_get_output(iot, 1);
41853a3f90c6SAndrew Thompson 
41863a3f90c6SAndrew Thompson 	/*
41873a3f90c6SAndrew Thompson 	 * check if there is only
41883a3f90c6SAndrew Thompson 	 * one output terminal:
41893a3f90c6SAndrew Thompson 	 */
41903a3f90c6SAndrew Thompson 	if (output[0] && (!output[1])) {
4191e2524b2eSHans Petter Selasky 		terminal_type =
4192e2524b2eSHans Petter Selasky 		    UGETW(output[0]->u.ot_v1->wTerminalType);
41933a3f90c6SAndrew Thompson 	}
41943a3f90c6SAndrew Thompson 	/*
41953a3f90c6SAndrew Thompson 	 * If the only output terminal is USB,
41963a3f90c6SAndrew Thompson 	 * the class is UAC_RECORD.
41973a3f90c6SAndrew Thompson 	 */
41983a3f90c6SAndrew Thompson 	if ((terminal_type & 0xff00) == (UAT_UNDEFINED & 0xff00)) {
41993a3f90c6SAndrew Thompson 
42003a3f90c6SAndrew Thompson 		mix->class = UAC_RECORD;
42013a3f90c6SAndrew Thompson 		if (input[0] && (!input[1])) {
4202e2524b2eSHans Petter Selasky 			terminal_type =
4203e2524b2eSHans Petter Selasky 			    UGETW(input[0]->u.it_v1->wTerminalType);
42043a3f90c6SAndrew Thompson 		} else {
42053a3f90c6SAndrew Thompson 			terminal_type = 0;
42063a3f90c6SAndrew Thompson 		}
42073a3f90c6SAndrew Thompson 		goto done;
42083a3f90c6SAndrew Thompson 	}
42093a3f90c6SAndrew Thompson 	/*
42103a3f90c6SAndrew Thompson 	 * if the unit is connected to just
42113a3f90c6SAndrew Thompson 	 * one input terminal, the
42123a3f90c6SAndrew Thompson 	 * class is UAC_INPUT:
42133a3f90c6SAndrew Thompson 	 */
42143a3f90c6SAndrew Thompson 	if (input[0] && (!input[1])) {
42153a3f90c6SAndrew Thompson 		mix->class = UAC_INPUT;
4216e2524b2eSHans Petter Selasky 		terminal_type =
4217e2524b2eSHans Petter Selasky 		    UGETW(input[0]->u.it_v1->wTerminalType);
4218e2524b2eSHans Petter Selasky 		goto done;
4219e2524b2eSHans Petter Selasky 	}
4220e2524b2eSHans Petter Selasky 	/*
4221e2524b2eSHans Petter Selasky 	 * Otherwise, the class is UAC_OUTPUT.
4222e2524b2eSHans Petter Selasky 	 */
4223e2524b2eSHans Petter Selasky 	mix->class = UAC_OUTPUT;
4224e2524b2eSHans Petter Selasky done:
4225e2524b2eSHans Petter Selasky 	return (terminal_type);
4226e2524b2eSHans Petter Selasky }
4227e2524b2eSHans Petter Selasky 
4228e2524b2eSHans Petter Selasky static uint16_t
4229e2524b2eSHans Petter Selasky uaudio20_mixer_determine_class(const struct uaudio_terminal_node *iot,
4230e2524b2eSHans Petter Selasky     struct uaudio_mixer_node *mix)
4231e2524b2eSHans Petter Selasky {
4232e2524b2eSHans Petter Selasky 	uint16_t terminal_type = 0x0000;
4233e2524b2eSHans Petter Selasky 	const struct uaudio_terminal_node *input[2];
4234e2524b2eSHans Petter Selasky 	const struct uaudio_terminal_node *output[2];
4235e2524b2eSHans Petter Selasky 
4236e2524b2eSHans Petter Selasky 	input[0] = uaudio_mixer_get_input(iot, 0);
4237e2524b2eSHans Petter Selasky 	input[1] = uaudio_mixer_get_input(iot, 1);
4238e2524b2eSHans Petter Selasky 
4239e2524b2eSHans Petter Selasky 	output[0] = uaudio_mixer_get_output(iot, 0);
4240e2524b2eSHans Petter Selasky 	output[1] = uaudio_mixer_get_output(iot, 1);
4241e2524b2eSHans Petter Selasky 
4242e2524b2eSHans Petter Selasky 	/*
4243e2524b2eSHans Petter Selasky 	 * check if there is only
4244e2524b2eSHans Petter Selasky 	 * one output terminal:
4245e2524b2eSHans Petter Selasky 	 */
4246e2524b2eSHans Petter Selasky 	if (output[0] && (!output[1]))
4247e2524b2eSHans Petter Selasky 		terminal_type = UGETW(output[0]->u.ot_v2->wTerminalType);
4248e2524b2eSHans Petter Selasky 	/*
4249e2524b2eSHans Petter Selasky 	 * If the only output terminal is USB,
4250e2524b2eSHans Petter Selasky 	 * the class is UAC_RECORD.
4251e2524b2eSHans Petter Selasky 	 */
4252e2524b2eSHans Petter Selasky 	if ((terminal_type & 0xff00) == (UAT_UNDEFINED & 0xff00)) {
4253e2524b2eSHans Petter Selasky 
4254e2524b2eSHans Petter Selasky 		mix->class = UAC_RECORD;
4255e2524b2eSHans Petter Selasky 		if (input[0] && (!input[1])) {
4256e2524b2eSHans Petter Selasky 			terminal_type =
4257e2524b2eSHans Petter Selasky 			    UGETW(input[0]->u.it_v2->wTerminalType);
4258e2524b2eSHans Petter Selasky 		} else {
4259e2524b2eSHans Petter Selasky 			terminal_type = 0;
4260e2524b2eSHans Petter Selasky 		}
4261e2524b2eSHans Petter Selasky 		goto done;
4262e2524b2eSHans Petter Selasky 	}
4263e2524b2eSHans Petter Selasky 	/*
4264e2524b2eSHans Petter Selasky 	 * if the unit is connected to just
4265e2524b2eSHans Petter Selasky 	 * one input terminal, the
4266e2524b2eSHans Petter Selasky 	 * class is UAC_INPUT:
4267e2524b2eSHans Petter Selasky 	 */
4268e2524b2eSHans Petter Selasky 	if (input[0] && (!input[1])) {
4269e2524b2eSHans Petter Selasky 		mix->class = UAC_INPUT;
4270e2524b2eSHans Petter Selasky 		terminal_type =
4271e2524b2eSHans Petter Selasky 		    UGETW(input[0]->u.it_v2->wTerminalType);
42723a3f90c6SAndrew Thompson 		goto done;
42733a3f90c6SAndrew Thompson 	}
42743a3f90c6SAndrew Thompson 	/*
42753a3f90c6SAndrew Thompson 	 * Otherwise, the class is UAC_OUTPUT.
42763a3f90c6SAndrew Thompson 	 */
42773a3f90c6SAndrew Thompson 	mix->class = UAC_OUTPUT;
42783a3f90c6SAndrew Thompson done:
42793a3f90c6SAndrew Thompson 	return (terminal_type);
42803a3f90c6SAndrew Thompson }
42813a3f90c6SAndrew Thompson 
42823a3f90c6SAndrew Thompson struct uaudio_tt_to_feature {
42833a3f90c6SAndrew Thompson 	uint16_t terminal_type;
42843a3f90c6SAndrew Thompson 	uint16_t feature;
42853a3f90c6SAndrew Thompson };
42863a3f90c6SAndrew Thompson 
42873a3f90c6SAndrew Thompson static const struct uaudio_tt_to_feature uaudio_tt_to_feature[] = {
42883a3f90c6SAndrew Thompson 
42893a3f90c6SAndrew Thompson 	{UAT_STREAM, SOUND_MIXER_PCM},
42903a3f90c6SAndrew Thompson 
42913a3f90c6SAndrew Thompson 	{UATI_MICROPHONE, SOUND_MIXER_MIC},
42923a3f90c6SAndrew Thompson 	{UATI_DESKMICROPHONE, SOUND_MIXER_MIC},
42933a3f90c6SAndrew Thompson 	{UATI_PERSONALMICROPHONE, SOUND_MIXER_MIC},
42943a3f90c6SAndrew Thompson 	{UATI_OMNIMICROPHONE, SOUND_MIXER_MIC},
42953a3f90c6SAndrew Thompson 	{UATI_MICROPHONEARRAY, SOUND_MIXER_MIC},
42963a3f90c6SAndrew Thompson 	{UATI_PROCMICROPHONEARR, SOUND_MIXER_MIC},
42973a3f90c6SAndrew Thompson 
42983a3f90c6SAndrew Thompson 	{UATO_SPEAKER, SOUND_MIXER_SPEAKER},
42993a3f90c6SAndrew Thompson 	{UATO_DESKTOPSPEAKER, SOUND_MIXER_SPEAKER},
43003a3f90c6SAndrew Thompson 	{UATO_ROOMSPEAKER, SOUND_MIXER_SPEAKER},
43013a3f90c6SAndrew Thompson 	{UATO_COMMSPEAKER, SOUND_MIXER_SPEAKER},
43023a3f90c6SAndrew Thompson 
43033a3f90c6SAndrew Thompson 	{UATE_ANALOGCONN, SOUND_MIXER_LINE},
43043a3f90c6SAndrew Thompson 	{UATE_LINECONN, SOUND_MIXER_LINE},
43053a3f90c6SAndrew Thompson 	{UATE_LEGACYCONN, SOUND_MIXER_LINE},
43063a3f90c6SAndrew Thompson 
43073a3f90c6SAndrew Thompson 	{UATE_DIGITALAUIFC, SOUND_MIXER_ALTPCM},
43083a3f90c6SAndrew Thompson 	{UATE_SPDIF, SOUND_MIXER_ALTPCM},
43093a3f90c6SAndrew Thompson 	{UATE_1394DA, SOUND_MIXER_ALTPCM},
43103a3f90c6SAndrew Thompson 	{UATE_1394DV, SOUND_MIXER_ALTPCM},
43113a3f90c6SAndrew Thompson 
43123a3f90c6SAndrew Thompson 	{UATF_CDPLAYER, SOUND_MIXER_CD},
43133a3f90c6SAndrew Thompson 
43143a3f90c6SAndrew Thompson 	{UATF_SYNTHESIZER, SOUND_MIXER_SYNTH},
43153a3f90c6SAndrew Thompson 
43163a3f90c6SAndrew Thompson 	{UATF_VIDEODISCAUDIO, SOUND_MIXER_VIDEO},
43173a3f90c6SAndrew Thompson 	{UATF_DVDAUDIO, SOUND_MIXER_VIDEO},
43183a3f90c6SAndrew Thompson 	{UATF_TVTUNERAUDIO, SOUND_MIXER_VIDEO},
43193a3f90c6SAndrew Thompson 
43203a3f90c6SAndrew Thompson 	/* telephony terminal types */
43213a3f90c6SAndrew Thompson 	{UATT_UNDEFINED, SOUND_MIXER_PHONEIN},	/* SOUND_MIXER_PHONEOUT */
43223a3f90c6SAndrew Thompson 	{UATT_PHONELINE, SOUND_MIXER_PHONEIN},	/* SOUND_MIXER_PHONEOUT */
43233a3f90c6SAndrew Thompson 	{UATT_TELEPHONE, SOUND_MIXER_PHONEIN},	/* SOUND_MIXER_PHONEOUT */
43243a3f90c6SAndrew Thompson 	{UATT_DOWNLINEPHONE, SOUND_MIXER_PHONEIN},	/* SOUND_MIXER_PHONEOUT */
43253a3f90c6SAndrew Thompson 
43263a3f90c6SAndrew Thompson 	{UATF_RADIORECV, SOUND_MIXER_RADIO},
43273a3f90c6SAndrew Thompson 	{UATF_RADIOXMIT, SOUND_MIXER_RADIO},
43283a3f90c6SAndrew Thompson 
43293a3f90c6SAndrew Thompson 	{UAT_UNDEFINED, SOUND_MIXER_VOLUME},
43303a3f90c6SAndrew Thompson 	{UAT_VENDOR, SOUND_MIXER_VOLUME},
43313a3f90c6SAndrew Thompson 	{UATI_UNDEFINED, SOUND_MIXER_VOLUME},
43323a3f90c6SAndrew Thompson 
43333a3f90c6SAndrew Thompson 	/* output terminal types */
43343a3f90c6SAndrew Thompson 	{UATO_UNDEFINED, SOUND_MIXER_VOLUME},
43353a3f90c6SAndrew Thompson 	{UATO_DISPLAYAUDIO, SOUND_MIXER_VOLUME},
43363a3f90c6SAndrew Thompson 	{UATO_SUBWOOFER, SOUND_MIXER_VOLUME},
43373a3f90c6SAndrew Thompson 	{UATO_HEADPHONES, SOUND_MIXER_VOLUME},
43383a3f90c6SAndrew Thompson 
43393a3f90c6SAndrew Thompson 	/* bidir terminal types */
43403a3f90c6SAndrew Thompson 	{UATB_UNDEFINED, SOUND_MIXER_VOLUME},
43413a3f90c6SAndrew Thompson 	{UATB_HANDSET, SOUND_MIXER_VOLUME},
43423a3f90c6SAndrew Thompson 	{UATB_HEADSET, SOUND_MIXER_VOLUME},
43433a3f90c6SAndrew Thompson 	{UATB_SPEAKERPHONE, SOUND_MIXER_VOLUME},
43443a3f90c6SAndrew Thompson 	{UATB_SPEAKERPHONEESUP, SOUND_MIXER_VOLUME},
43453a3f90c6SAndrew Thompson 	{UATB_SPEAKERPHONEECANC, SOUND_MIXER_VOLUME},
43463a3f90c6SAndrew Thompson 
43473a3f90c6SAndrew Thompson 	/* external terminal types */
43483a3f90c6SAndrew Thompson 	{UATE_UNDEFINED, SOUND_MIXER_VOLUME},
43493a3f90c6SAndrew Thompson 
43503a3f90c6SAndrew Thompson 	/* embedded function terminal types */
43513a3f90c6SAndrew Thompson 	{UATF_UNDEFINED, SOUND_MIXER_VOLUME},
43523a3f90c6SAndrew Thompson 	{UATF_CALIBNOISE, SOUND_MIXER_VOLUME},
43533a3f90c6SAndrew Thompson 	{UATF_EQUNOISE, SOUND_MIXER_VOLUME},
43543a3f90c6SAndrew Thompson 	{UATF_DAT, SOUND_MIXER_VOLUME},
43553a3f90c6SAndrew Thompson 	{UATF_DCC, SOUND_MIXER_VOLUME},
43563a3f90c6SAndrew Thompson 	{UATF_MINIDISK, SOUND_MIXER_VOLUME},
43573a3f90c6SAndrew Thompson 	{UATF_ANALOGTAPE, SOUND_MIXER_VOLUME},
43583a3f90c6SAndrew Thompson 	{UATF_PHONOGRAPH, SOUND_MIXER_VOLUME},
43593a3f90c6SAndrew Thompson 	{UATF_VCRAUDIO, SOUND_MIXER_VOLUME},
43603a3f90c6SAndrew Thompson 	{UATF_SATELLITE, SOUND_MIXER_VOLUME},
43613a3f90c6SAndrew Thompson 	{UATF_CABLETUNER, SOUND_MIXER_VOLUME},
43623a3f90c6SAndrew Thompson 	{UATF_DSS, SOUND_MIXER_VOLUME},
43633a3f90c6SAndrew Thompson 	{UATF_MULTITRACK, SOUND_MIXER_VOLUME},
43643a3f90c6SAndrew Thompson 	{0xffff, SOUND_MIXER_VOLUME},
43653a3f90c6SAndrew Thompson 
43663a3f90c6SAndrew Thompson 	/* default */
43673a3f90c6SAndrew Thompson 	{0x0000, SOUND_MIXER_VOLUME},
43683a3f90c6SAndrew Thompson };
43693a3f90c6SAndrew Thompson 
43703a3f90c6SAndrew Thompson static uint16_t
43713a3f90c6SAndrew Thompson uaudio_mixer_feature_name(const struct uaudio_terminal_node *iot,
43723a3f90c6SAndrew Thompson     struct uaudio_mixer_node *mix)
43733a3f90c6SAndrew Thompson {
43743a3f90c6SAndrew Thompson 	const struct uaudio_tt_to_feature *uat = uaudio_tt_to_feature;
43753a3f90c6SAndrew Thompson 	uint16_t terminal_type = uaudio_mixer_determine_class(iot, mix);
43763a3f90c6SAndrew Thompson 
43773a3f90c6SAndrew Thompson 	if ((mix->class == UAC_RECORD) && (terminal_type == 0)) {
43783a3f90c6SAndrew Thompson 		return (SOUND_MIXER_IMIX);
43793a3f90c6SAndrew Thompson 	}
43803a3f90c6SAndrew Thompson 	while (uat->terminal_type) {
43813a3f90c6SAndrew Thompson 		if (uat->terminal_type == terminal_type) {
43823a3f90c6SAndrew Thompson 			break;
43833a3f90c6SAndrew Thompson 		}
43843a3f90c6SAndrew Thompson 		uat++;
43853a3f90c6SAndrew Thompson 	}
43863a3f90c6SAndrew Thompson 
4387e2524b2eSHans Petter Selasky 	DPRINTF("terminal_type=0x%04x -> %d\n",
4388e2524b2eSHans Petter Selasky 	    terminal_type, uat->feature);
4389e2524b2eSHans Petter Selasky 
4390e2524b2eSHans Petter Selasky 	return (uat->feature);
4391e2524b2eSHans Petter Selasky }
4392e2524b2eSHans Petter Selasky 
4393e2524b2eSHans Petter Selasky static uint16_t
4394e2524b2eSHans Petter Selasky uaudio20_mixer_feature_name(const struct uaudio_terminal_node *iot,
4395e2524b2eSHans Petter Selasky     struct uaudio_mixer_node *mix)
4396e2524b2eSHans Petter Selasky {
4397e2524b2eSHans Petter Selasky 	const struct uaudio_tt_to_feature *uat;
4398e2524b2eSHans Petter Selasky 	uint16_t terminal_type = uaudio20_mixer_determine_class(iot, mix);
4399e2524b2eSHans Petter Selasky 
4400e2524b2eSHans Petter Selasky 	if ((mix->class == UAC_RECORD) && (terminal_type == 0))
4401e2524b2eSHans Petter Selasky 		return (SOUND_MIXER_IMIX);
4402e2524b2eSHans Petter Selasky 
4403e2524b2eSHans Petter Selasky 	for (uat = uaudio_tt_to_feature; uat->terminal_type != 0; uat++) {
4404e2524b2eSHans Petter Selasky 		if (uat->terminal_type == terminal_type)
4405e2524b2eSHans Petter Selasky 			break;
4406e2524b2eSHans Petter Selasky 	}
4407e2524b2eSHans Petter Selasky 
4408e2524b2eSHans Petter Selasky 	DPRINTF("terminal_type=0x%04x -> %d\n",
44093a3f90c6SAndrew Thompson 	    terminal_type, uat->feature);
44103a3f90c6SAndrew Thompson 
44113a3f90c6SAndrew Thompson 	return (uat->feature);
44123a3f90c6SAndrew Thompson }
44133a3f90c6SAndrew Thompson 
44146d917491SHans Petter Selasky static const struct uaudio_terminal_node *
44156d917491SHans Petter Selasky uaudio_mixer_get_input(const struct uaudio_terminal_node *iot, uint8_t i)
44163a3f90c6SAndrew Thompson {
44173a3f90c6SAndrew Thompson 	struct uaudio_terminal_node *root = iot->root;
44183a3f90c6SAndrew Thompson 	uint8_t n;
44193a3f90c6SAndrew Thompson 
44203a3f90c6SAndrew Thompson 	n = iot->usr.id_max;
44213a3f90c6SAndrew Thompson 	do {
44223a3f90c6SAndrew Thompson 		if (iot->usr.bit_input[n / 8] & (1 << (n % 8))) {
44236d917491SHans Petter Selasky 			if (!i--)
44243a3f90c6SAndrew Thompson 				return (root + n);
44253a3f90c6SAndrew Thompson 		}
44263a3f90c6SAndrew Thompson 	} while (n--);
44273a3f90c6SAndrew Thompson 
44283a3f90c6SAndrew Thompson 	return (NULL);
44293a3f90c6SAndrew Thompson }
44303a3f90c6SAndrew Thompson 
44316d917491SHans Petter Selasky static const struct uaudio_terminal_node *
44326d917491SHans Petter Selasky uaudio_mixer_get_output(const struct uaudio_terminal_node *iot, uint8_t i)
44333a3f90c6SAndrew Thompson {
44343a3f90c6SAndrew Thompson 	struct uaudio_terminal_node *root = iot->root;
44353a3f90c6SAndrew Thompson 	uint8_t n;
44363a3f90c6SAndrew Thompson 
44373a3f90c6SAndrew Thompson 	n = iot->usr.id_max;
44383a3f90c6SAndrew Thompson 	do {
44393a3f90c6SAndrew Thompson 		if (iot->usr.bit_output[n / 8] & (1 << (n % 8))) {
44406d917491SHans Petter Selasky 			if (!i--)
44413a3f90c6SAndrew Thompson 				return (root + n);
44423a3f90c6SAndrew Thompson 		}
44433a3f90c6SAndrew Thompson 	} while (n--);
44443a3f90c6SAndrew Thompson 
44453a3f90c6SAndrew Thompson 	return (NULL);
44463a3f90c6SAndrew Thompson }
44473a3f90c6SAndrew Thompson 
44483a3f90c6SAndrew Thompson static void
44493a3f90c6SAndrew Thompson uaudio_mixer_find_inputs_sub(struct uaudio_terminal_node *root,
44503a3f90c6SAndrew Thompson     const uint8_t *p_id, uint8_t n_id,
44513a3f90c6SAndrew Thompson     struct uaudio_search_result *info)
44523a3f90c6SAndrew Thompson {
44533a3f90c6SAndrew Thompson 	struct uaudio_terminal_node *iot;
44543a3f90c6SAndrew Thompson 	uint8_t n;
44553a3f90c6SAndrew Thompson 	uint8_t i;
4456e2524b2eSHans Petter Selasky 	uint8_t is_last;
44573a3f90c6SAndrew Thompson 
4458e2524b2eSHans Petter Selasky top:
44593a3f90c6SAndrew Thompson 	for (n = 0; n < n_id; n++) {
44603a3f90c6SAndrew Thompson 
44613a3f90c6SAndrew Thompson 		i = p_id[n];
44623a3f90c6SAndrew Thompson 
4463e2524b2eSHans Petter Selasky 		if (info->recurse_level == UAUDIO_RECURSE_LIMIT) {
44643a3f90c6SAndrew Thompson 			DPRINTF("avoided going into a circle at id=%d!\n", i);
4465e2524b2eSHans Petter Selasky 			return;
44663a3f90c6SAndrew Thompson 		}
44673a3f90c6SAndrew Thompson 
4468e2524b2eSHans Petter Selasky 		info->recurse_level++;
4469e2524b2eSHans Petter Selasky 
44703a3f90c6SAndrew Thompson 		iot = (root + i);
44713a3f90c6SAndrew Thompson 
4472e2524b2eSHans Petter Selasky 		if (iot->u.desc == NULL)
44733a3f90c6SAndrew Thompson 			continue;
4474e2524b2eSHans Petter Selasky 
4475e2524b2eSHans Petter Selasky 		is_last = ((n + 1) == n_id);
4476e2524b2eSHans Petter Selasky 
44773a3f90c6SAndrew Thompson 		switch (iot->u.desc->bDescriptorSubtype) {
44783a3f90c6SAndrew Thompson 		case UDESCSUB_AC_INPUT:
44793a3f90c6SAndrew Thompson 			info->bit_input[i / 8] |= (1 << (i % 8));
44803a3f90c6SAndrew Thompson 			break;
44813a3f90c6SAndrew Thompson 
44823a3f90c6SAndrew Thompson 		case UDESCSUB_AC_FEATURE:
4483e2524b2eSHans Petter Selasky 			if (is_last) {
4484e2524b2eSHans Petter Selasky 				p_id = &iot->u.fu_v1->bSourceId;
4485e2524b2eSHans Petter Selasky 				n_id = 1;
4486e2524b2eSHans Petter Selasky 				goto top;
4487e2524b2eSHans Petter Selasky 			}
4488e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
4489e2524b2eSHans Petter Selasky 			    root, &iot->u.fu_v1->bSourceId, 1, info);
44903a3f90c6SAndrew Thompson 			break;
44913a3f90c6SAndrew Thompson 
44923a3f90c6SAndrew Thompson 		case UDESCSUB_AC_OUTPUT:
4493e2524b2eSHans Petter Selasky 			if (is_last) {
4494e2524b2eSHans Petter Selasky 				p_id = &iot->u.ot_v1->bSourceId;
4495e2524b2eSHans Petter Selasky 				n_id = 1;
4496e2524b2eSHans Petter Selasky 				goto top;
4497e2524b2eSHans Petter Selasky 			}
4498e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
4499e2524b2eSHans Petter Selasky 			    root, &iot->u.ot_v1->bSourceId, 1, info);
45003a3f90c6SAndrew Thompson 			break;
45013a3f90c6SAndrew Thompson 
45023a3f90c6SAndrew Thompson 		case UDESCSUB_AC_MIXER:
4503e2524b2eSHans Petter Selasky 			if (is_last) {
4504e2524b2eSHans Petter Selasky 				p_id = iot->u.mu_v1->baSourceId;
4505e2524b2eSHans Petter Selasky 				n_id = iot->u.mu_v1->bNrInPins;
4506e2524b2eSHans Petter Selasky 				goto top;
4507e2524b2eSHans Petter Selasky 			}
4508e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
4509e2524b2eSHans Petter Selasky 			    root, iot->u.mu_v1->baSourceId,
4510e2524b2eSHans Petter Selasky 			    iot->u.mu_v1->bNrInPins, info);
45113a3f90c6SAndrew Thompson 			break;
45123a3f90c6SAndrew Thompson 
45133a3f90c6SAndrew Thompson 		case UDESCSUB_AC_SELECTOR:
4514e2524b2eSHans Petter Selasky 			if (is_last) {
4515e2524b2eSHans Petter Selasky 				p_id = iot->u.su_v1->baSourceId;
4516e2524b2eSHans Petter Selasky 				n_id = iot->u.su_v1->bNrInPins;
4517e2524b2eSHans Petter Selasky 				goto top;
4518e2524b2eSHans Petter Selasky 			}
4519e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
4520e2524b2eSHans Petter Selasky 			    root, iot->u.su_v1->baSourceId,
4521e2524b2eSHans Petter Selasky 			    iot->u.su_v1->bNrInPins, info);
45223a3f90c6SAndrew Thompson 			break;
45233a3f90c6SAndrew Thompson 
45243a3f90c6SAndrew Thompson 		case UDESCSUB_AC_PROCESSING:
4525e2524b2eSHans Petter Selasky 			if (is_last) {
4526e2524b2eSHans Petter Selasky 				p_id = iot->u.pu_v1->baSourceId;
4527e2524b2eSHans Petter Selasky 				n_id = iot->u.pu_v1->bNrInPins;
4528e2524b2eSHans Petter Selasky 				goto top;
4529e2524b2eSHans Petter Selasky 			}
4530e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
4531e2524b2eSHans Petter Selasky 			    root, iot->u.pu_v1->baSourceId,
4532e2524b2eSHans Petter Selasky 			    iot->u.pu_v1->bNrInPins, info);
45333a3f90c6SAndrew Thompson 			break;
45343a3f90c6SAndrew Thompson 
45353a3f90c6SAndrew Thompson 		case UDESCSUB_AC_EXTENSION:
4536e2524b2eSHans Petter Selasky 			if (is_last) {
4537e2524b2eSHans Petter Selasky 				p_id = iot->u.eu_v1->baSourceId;
4538e2524b2eSHans Petter Selasky 				n_id = iot->u.eu_v1->bNrInPins;
4539e2524b2eSHans Petter Selasky 				goto top;
4540e2524b2eSHans Petter Selasky 			}
4541e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
4542e2524b2eSHans Petter Selasky 			    root, iot->u.eu_v1->baSourceId,
4543e2524b2eSHans Petter Selasky 			    iot->u.eu_v1->bNrInPins, info);
45443a3f90c6SAndrew Thompson 			break;
45453a3f90c6SAndrew Thompson 
45463a3f90c6SAndrew Thompson 		default:
45473a3f90c6SAndrew Thompson 			break;
45483a3f90c6SAndrew Thompson 		}
45493a3f90c6SAndrew Thompson 	}
4550e2524b2eSHans Petter Selasky }
4551e2524b2eSHans Petter Selasky 
4552e2524b2eSHans Petter Selasky static void
4553e2524b2eSHans Petter Selasky uaudio20_mixer_find_inputs_sub(struct uaudio_terminal_node *root,
4554e2524b2eSHans Petter Selasky     const uint8_t *p_id, uint8_t n_id,
4555e2524b2eSHans Petter Selasky     struct uaudio_search_result *info)
4556e2524b2eSHans Petter Selasky {
4557e2524b2eSHans Petter Selasky 	struct uaudio_terminal_node *iot;
4558e2524b2eSHans Petter Selasky 	uint8_t n;
4559e2524b2eSHans Petter Selasky 	uint8_t i;
4560e2524b2eSHans Petter Selasky 	uint8_t is_last;
4561e2524b2eSHans Petter Selasky 
4562e2524b2eSHans Petter Selasky top:
4563e2524b2eSHans Petter Selasky 	for (n = 0; n < n_id; n++) {
4564e2524b2eSHans Petter Selasky 
4565e2524b2eSHans Petter Selasky 		i = p_id[n];
4566e2524b2eSHans Petter Selasky 
4567e2524b2eSHans Petter Selasky 		if (info->recurse_level == UAUDIO_RECURSE_LIMIT) {
4568e2524b2eSHans Petter Selasky 			DPRINTF("avoided going into a circle at id=%d!\n", i);
4569e2524b2eSHans Petter Selasky 			return;
4570e2524b2eSHans Petter Selasky 		}
4571e2524b2eSHans Petter Selasky 
4572e2524b2eSHans Petter Selasky 		info->recurse_level++;
4573e2524b2eSHans Petter Selasky 
4574e2524b2eSHans Petter Selasky 		iot = (root + i);
4575e2524b2eSHans Petter Selasky 
4576e2524b2eSHans Petter Selasky 		if (iot->u.desc == NULL)
4577e2524b2eSHans Petter Selasky 			continue;
4578e2524b2eSHans Petter Selasky 
4579e2524b2eSHans Petter Selasky 		is_last = ((n + 1) == n_id);
4580e2524b2eSHans Petter Selasky 
4581e2524b2eSHans Petter Selasky 		switch (iot->u.desc->bDescriptorSubtype) {
4582e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_INPUT:
4583e2524b2eSHans Petter Selasky 			info->bit_input[i / 8] |= (1 << (i % 8));
4584e2524b2eSHans Petter Selasky 			break;
4585e2524b2eSHans Petter Selasky 
4586e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_OUTPUT:
4587e2524b2eSHans Petter Selasky 			if (is_last) {
4588e2524b2eSHans Petter Selasky 				p_id = &iot->u.ot_v2->bSourceId;
4589e2524b2eSHans Petter Selasky 				n_id = 1;
4590e2524b2eSHans Petter Selasky 				goto top;
4591e2524b2eSHans Petter Selasky 			}
4592e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4593e2524b2eSHans Petter Selasky 			    root, &iot->u.ot_v2->bSourceId, 1, info);
4594e2524b2eSHans Petter Selasky 			break;
4595e2524b2eSHans Petter Selasky 
4596e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_MIXER:
4597e2524b2eSHans Petter Selasky 			if (is_last) {
4598e2524b2eSHans Petter Selasky 				p_id = iot->u.mu_v2->baSourceId;
4599e2524b2eSHans Petter Selasky 				n_id = iot->u.mu_v2->bNrInPins;
4600e2524b2eSHans Petter Selasky 				goto top;
4601e2524b2eSHans Petter Selasky 			}
4602e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4603e2524b2eSHans Petter Selasky 			    root, iot->u.mu_v2->baSourceId,
4604e2524b2eSHans Petter Selasky 			    iot->u.mu_v2->bNrInPins, info);
4605e2524b2eSHans Petter Selasky 			break;
4606e2524b2eSHans Petter Selasky 
4607e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_SELECTOR:
4608e2524b2eSHans Petter Selasky 			if (is_last) {
4609e2524b2eSHans Petter Selasky 				p_id = iot->u.su_v2->baSourceId;
4610e2524b2eSHans Petter Selasky 				n_id = iot->u.su_v2->bNrInPins;
4611e2524b2eSHans Petter Selasky 				goto top;
4612e2524b2eSHans Petter Selasky 			}
4613e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4614e2524b2eSHans Petter Selasky 			    root, iot->u.su_v2->baSourceId,
4615e2524b2eSHans Petter Selasky 			    iot->u.su_v2->bNrInPins, info);
4616e2524b2eSHans Petter Selasky 			break;
4617e2524b2eSHans Petter Selasky 
4618e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_SAMPLE_RT:
4619e2524b2eSHans Petter Selasky 			if (is_last) {
4620e2524b2eSHans Petter Selasky 				p_id = &iot->u.ru_v2->bSourceId;
4621e2524b2eSHans Petter Selasky 				n_id = 1;
4622e2524b2eSHans Petter Selasky 				goto top;
4623e2524b2eSHans Petter Selasky 			}
4624e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4625e2524b2eSHans Petter Selasky 			    root, &iot->u.ru_v2->bSourceId,
4626e2524b2eSHans Petter Selasky 			    1, info);
4627e2524b2eSHans Petter Selasky 			break;
4628e2524b2eSHans Petter Selasky 
4629e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_EFFECT:
4630e2524b2eSHans Petter Selasky 			if (is_last) {
4631e2524b2eSHans Petter Selasky 				p_id = &iot->u.ef_v2->bSourceId;
4632e2524b2eSHans Petter Selasky 				n_id = 1;
4633e2524b2eSHans Petter Selasky 				goto top;
4634e2524b2eSHans Petter Selasky 			}
4635e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4636e2524b2eSHans Petter Selasky 			    root, &iot->u.ef_v2->bSourceId,
4637e2524b2eSHans Petter Selasky 			    1, info);
4638e2524b2eSHans Petter Selasky 			break;
4639e2524b2eSHans Petter Selasky 
4640e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_FEATURE:
4641e2524b2eSHans Petter Selasky 			if (is_last) {
4642e2524b2eSHans Petter Selasky 				p_id = &iot->u.fu_v2->bSourceId;
4643e2524b2eSHans Petter Selasky 				n_id = 1;
4644e2524b2eSHans Petter Selasky 				goto top;
4645e2524b2eSHans Petter Selasky 			}
4646e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4647e2524b2eSHans Petter Selasky 			    root, &iot->u.fu_v2->bSourceId, 1, info);
4648e2524b2eSHans Petter Selasky 			break;
4649e2524b2eSHans Petter Selasky 
4650e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_PROCESSING_V2:
4651e2524b2eSHans Petter Selasky 			if (is_last) {
4652e2524b2eSHans Petter Selasky 				p_id = iot->u.pu_v2->baSourceId;
4653e2524b2eSHans Petter Selasky 				n_id = iot->u.pu_v2->bNrInPins;
4654e2524b2eSHans Petter Selasky 				goto top;
4655e2524b2eSHans Petter Selasky 			}
4656e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4657e2524b2eSHans Petter Selasky 			    root, iot->u.pu_v2->baSourceId,
4658e2524b2eSHans Petter Selasky 			    iot->u.pu_v2->bNrInPins, info);
4659e2524b2eSHans Petter Selasky 			break;
4660e2524b2eSHans Petter Selasky 
4661e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_EXTENSION_V2:
4662e2524b2eSHans Petter Selasky 			if (is_last) {
4663e2524b2eSHans Petter Selasky 				p_id = iot->u.eu_v2->baSourceId;
4664e2524b2eSHans Petter Selasky 				n_id = iot->u.eu_v2->bNrInPins;
4665e2524b2eSHans Petter Selasky 				goto top;
4666e2524b2eSHans Petter Selasky 			}
4667e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4668e2524b2eSHans Petter Selasky 			    root, iot->u.eu_v2->baSourceId,
4669e2524b2eSHans Petter Selasky 			    iot->u.eu_v2->bNrInPins, info);
4670e2524b2eSHans Petter Selasky 			break;
4671e2524b2eSHans Petter Selasky 		default:
4672e2524b2eSHans Petter Selasky 			break;
4673e2524b2eSHans Petter Selasky 		}
4674e2524b2eSHans Petter Selasky 	}
4675e2524b2eSHans Petter Selasky }
4676e2524b2eSHans Petter Selasky 
4677e2524b2eSHans Petter Selasky static void
4678e2524b2eSHans Petter Selasky uaudio20_mixer_find_clocks_sub(struct uaudio_terminal_node *root,
4679e2524b2eSHans Petter Selasky     const uint8_t *p_id, uint8_t n_id,
4680e2524b2eSHans Petter Selasky     struct uaudio_search_result *info)
4681e2524b2eSHans Petter Selasky {
4682e2524b2eSHans Petter Selasky 	struct uaudio_terminal_node *iot;
4683e2524b2eSHans Petter Selasky 	uint8_t n;
4684e2524b2eSHans Petter Selasky 	uint8_t i;
4685e2524b2eSHans Petter Selasky 	uint8_t is_last;
4686e2524b2eSHans Petter Selasky 	uint8_t id;
4687e2524b2eSHans Petter Selasky 
4688e2524b2eSHans Petter Selasky top:
4689e2524b2eSHans Petter Selasky 	for (n = 0; n < n_id; n++) {
4690e2524b2eSHans Petter Selasky 
4691e2524b2eSHans Petter Selasky 		i = p_id[n];
4692e2524b2eSHans Petter Selasky 
4693e2524b2eSHans Petter Selasky 		if (info->recurse_level == UAUDIO_RECURSE_LIMIT) {
4694e2524b2eSHans Petter Selasky 			DPRINTF("avoided going into a circle at id=%d!\n", i);
4695e2524b2eSHans Petter Selasky 			return;
4696e2524b2eSHans Petter Selasky 		}
4697e2524b2eSHans Petter Selasky 
4698e2524b2eSHans Petter Selasky 		info->recurse_level++;
4699e2524b2eSHans Petter Selasky 
4700e2524b2eSHans Petter Selasky 		iot = (root + i);
4701e2524b2eSHans Petter Selasky 
4702e2524b2eSHans Petter Selasky 		if (iot->u.desc == NULL)
4703e2524b2eSHans Petter Selasky 			continue;
4704e2524b2eSHans Petter Selasky 
4705e2524b2eSHans Petter Selasky 		is_last = ((n + 1) == n_id);
4706e2524b2eSHans Petter Selasky 
4707e2524b2eSHans Petter Selasky 		switch (iot->u.desc->bDescriptorSubtype) {
4708e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_INPUT:
4709e2524b2eSHans Petter Selasky 			info->is_input = 1;
4710e2524b2eSHans Petter Selasky 			if (is_last) {
4711e2524b2eSHans Petter Selasky 				p_id = &iot->u.it_v2->bCSourceId;
4712e2524b2eSHans Petter Selasky 				n_id = 1;
4713e2524b2eSHans Petter Selasky 				goto top;
4714e2524b2eSHans Petter Selasky 			}
4715e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_clocks_sub(root,
4716e2524b2eSHans Petter Selasky 			    &iot->u.it_v2->bCSourceId, 1, info);
4717e2524b2eSHans Petter Selasky 			break;
4718e2524b2eSHans Petter Selasky 
4719e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_OUTPUT:
4720e2524b2eSHans Petter Selasky 			info->is_input = 0;
4721e2524b2eSHans Petter Selasky 			if (is_last) {
4722e2524b2eSHans Petter Selasky 				p_id = &iot->u.ot_v2->bCSourceId;
4723e2524b2eSHans Petter Selasky 				n_id = 1;
4724e2524b2eSHans Petter Selasky 				goto top;
4725e2524b2eSHans Petter Selasky 			}
4726e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_clocks_sub(root,
4727e2524b2eSHans Petter Selasky 			    &iot->u.ot_v2->bCSourceId, 1, info);
4728e2524b2eSHans Petter Selasky 			break;
4729e2524b2eSHans Petter Selasky 
4730e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_CLOCK_SEL:
4731e2524b2eSHans Petter Selasky 			if (is_last) {
4732e2524b2eSHans Petter Selasky 				p_id = iot->u.csel_v2->baCSourceId;
4733e2524b2eSHans Petter Selasky 				n_id = iot->u.csel_v2->bNrInPins;
4734e2524b2eSHans Petter Selasky 				goto top;
4735e2524b2eSHans Petter Selasky 			}
4736e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_clocks_sub(root,
4737e2524b2eSHans Petter Selasky 			    iot->u.csel_v2->baCSourceId,
4738e2524b2eSHans Petter Selasky 			    iot->u.csel_v2->bNrInPins, info);
4739e2524b2eSHans Petter Selasky 			break;
4740e2524b2eSHans Petter Selasky 
4741e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_CLOCK_MUL:
4742e2524b2eSHans Petter Selasky 			if (is_last) {
4743e2524b2eSHans Petter Selasky 				p_id = &iot->u.cmul_v2->bCSourceId;
4744e2524b2eSHans Petter Selasky 				n_id = 1;
4745e2524b2eSHans Petter Selasky 				goto top;
4746e2524b2eSHans Petter Selasky 			}
4747e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_clocks_sub(root,
4748e2524b2eSHans Petter Selasky 			    &iot->u.cmul_v2->bCSourceId,
4749e2524b2eSHans Petter Selasky 			    1, info);
4750e2524b2eSHans Petter Selasky 			break;
4751e2524b2eSHans Petter Selasky 
4752e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_CLOCK_SRC:
4753e2524b2eSHans Petter Selasky 
4754e2524b2eSHans Petter Selasky 			id = iot->u.csrc_v2->bClockId;
4755e2524b2eSHans Petter Selasky 
4756e2524b2eSHans Petter Selasky 			switch (info->is_input) {
4757e2524b2eSHans Petter Selasky 			case 0:
4758e2524b2eSHans Petter Selasky 				info->bit_output[id / 8] |= (1 << (id % 8));
4759e2524b2eSHans Petter Selasky 				break;
4760e2524b2eSHans Petter Selasky 			case 1:
4761e2524b2eSHans Petter Selasky 				info->bit_input[id / 8] |= (1 << (id % 8));
4762e2524b2eSHans Petter Selasky 				break;
4763e2524b2eSHans Petter Selasky 			default:
4764e2524b2eSHans Petter Selasky 				break;
4765e2524b2eSHans Petter Selasky 			}
4766e2524b2eSHans Petter Selasky 			break;
4767e2524b2eSHans Petter Selasky 
4768e2524b2eSHans Petter Selasky 		default:
4769e2524b2eSHans Petter Selasky 			break;
4770e2524b2eSHans Petter Selasky 		}
4771e2524b2eSHans Petter Selasky 	}
47723a3f90c6SAndrew Thompson }
47733a3f90c6SAndrew Thompson 
47743a3f90c6SAndrew Thompson static void
47753a3f90c6SAndrew Thompson uaudio_mixer_find_outputs_sub(struct uaudio_terminal_node *root, uint8_t id,
47763a3f90c6SAndrew Thompson     uint8_t n_id, struct uaudio_search_result *info)
47773a3f90c6SAndrew Thompson {
47783a3f90c6SAndrew Thompson 	struct uaudio_terminal_node *iot = (root + id);
47793a3f90c6SAndrew Thompson 	uint8_t j;
47803a3f90c6SAndrew Thompson 
47813a3f90c6SAndrew Thompson 	j = n_id;
47823a3f90c6SAndrew Thompson 	do {
47833a3f90c6SAndrew Thompson 		if ((j != id) && ((root + j)->u.desc) &&
47843a3f90c6SAndrew Thompson 		    ((root + j)->u.desc->bDescriptorSubtype == UDESCSUB_AC_OUTPUT)) {
47853a3f90c6SAndrew Thompson 
47863a3f90c6SAndrew Thompson 			/*
47873a3f90c6SAndrew Thompson 			 * "j" (output) <--- virtual wire <--- "id" (input)
47883a3f90c6SAndrew Thompson 			 *
47893a3f90c6SAndrew Thompson 			 * if "j" has "id" on the input, then "id" have "j" on
47903a3f90c6SAndrew Thompson 			 * the output, because they are connected:
47913a3f90c6SAndrew Thompson 			 */
47923a3f90c6SAndrew Thompson 			if ((root + j)->usr.bit_input[id / 8] & (1 << (id % 8))) {
47933a3f90c6SAndrew Thompson 				iot->usr.bit_output[j / 8] |= (1 << (j % 8));
47943a3f90c6SAndrew Thompson 			}
47953a3f90c6SAndrew Thompson 		}
47963a3f90c6SAndrew Thompson 	} while (j--);
47973a3f90c6SAndrew Thompson }
47983a3f90c6SAndrew Thompson 
47993a3f90c6SAndrew Thompson static void
4800e2524b2eSHans Petter Selasky uaudio_mixer_fill_info(struct uaudio_softc *sc,
4801e2524b2eSHans Petter Selasky     struct usb_device *udev, void *desc)
48023a3f90c6SAndrew Thompson {
48034c21be9bSRebecca Cran 	const struct usb_audio_control_descriptor *acdp;
4804a593f6b8SAndrew Thompson 	struct usb_config_descriptor *cd = usbd_get_config_descriptor(udev);
4805760bc48eSAndrew Thompson 	const struct usb_descriptor *dp;
48064c21be9bSRebecca Cran 	const struct usb_audio_unit *au;
48073a3f90c6SAndrew Thompson 	struct uaudio_terminal_node *iot = NULL;
48083a3f90c6SAndrew Thompson 	uint16_t wTotalLen;
48093a3f90c6SAndrew Thompson 	uint8_t ID_max = 0;		/* inclusive */
48103a3f90c6SAndrew Thompson 	uint8_t i;
48113a3f90c6SAndrew Thompson 
4812a593f6b8SAndrew Thompson 	desc = usb_desc_foreach(cd, desc);
48133a3f90c6SAndrew Thompson 
48143a3f90c6SAndrew Thompson 	if (desc == NULL) {
48153a3f90c6SAndrew Thompson 		DPRINTF("no Audio Control header\n");
48163a3f90c6SAndrew Thompson 		goto done;
48173a3f90c6SAndrew Thompson 	}
48183a3f90c6SAndrew Thompson 	acdp = desc;
48193a3f90c6SAndrew Thompson 
48203a3f90c6SAndrew Thompson 	if ((acdp->bLength < sizeof(*acdp)) ||
48213a3f90c6SAndrew Thompson 	    (acdp->bDescriptorType != UDESC_CS_INTERFACE) ||
48223a3f90c6SAndrew Thompson 	    (acdp->bDescriptorSubtype != UDESCSUB_AC_HEADER)) {
48233a3f90c6SAndrew Thompson 		DPRINTF("invalid Audio Control header\n");
48243a3f90c6SAndrew Thompson 		goto done;
48253a3f90c6SAndrew Thompson 	}
48263a3f90c6SAndrew Thompson 	/* "wTotalLen" is allowed to be corrupt */
48273a3f90c6SAndrew Thompson 	wTotalLen = UGETW(acdp->wTotalLength) - acdp->bLength;
48283a3f90c6SAndrew Thompson 
48293a3f90c6SAndrew Thompson 	/* get USB audio revision */
48303a3f90c6SAndrew Thompson 	sc->sc_audio_rev = UGETW(acdp->bcdADC);
48313a3f90c6SAndrew Thompson 
48323a3f90c6SAndrew Thompson 	DPRINTFN(3, "found AC header, vers=%03x, len=%d\n",
48333a3f90c6SAndrew Thompson 	    sc->sc_audio_rev, wTotalLen);
48343a3f90c6SAndrew Thompson 
48353a3f90c6SAndrew Thompson 	iot = malloc(sizeof(struct uaudio_terminal_node) * 256, M_TEMP,
48363a3f90c6SAndrew Thompson 	    M_WAITOK | M_ZERO);
48373a3f90c6SAndrew Thompson 
48383a3f90c6SAndrew Thompson 	if (iot == NULL) {
48393a3f90c6SAndrew Thompson 		DPRINTF("no memory!\n");
48403a3f90c6SAndrew Thompson 		goto done;
48413a3f90c6SAndrew Thompson 	}
4842a593f6b8SAndrew Thompson 	while ((desc = usb_desc_foreach(cd, desc))) {
48433a3f90c6SAndrew Thompson 
48443a3f90c6SAndrew Thompson 		dp = desc;
48453a3f90c6SAndrew Thompson 
48463a3f90c6SAndrew Thompson 		if (dp->bLength > wTotalLen) {
48473a3f90c6SAndrew Thompson 			break;
48483a3f90c6SAndrew Thompson 		} else {
48493a3f90c6SAndrew Thompson 			wTotalLen -= dp->bLength;
48503a3f90c6SAndrew Thompson 		}
48513a3f90c6SAndrew Thompson 
4852e2524b2eSHans Petter Selasky 		if (sc->sc_audio_rev >= UAUDIO_VERSION_30)
4853e2524b2eSHans Petter Selasky 			au = NULL;
4854e2524b2eSHans Petter Selasky 		else if (sc->sc_audio_rev >= UAUDIO_VERSION_20)
4855e2524b2eSHans Petter Selasky 			au = uaudio20_mixer_verify_desc(dp, 0);
4856e2524b2eSHans Petter Selasky 		else
48573a3f90c6SAndrew Thompson 			au = uaudio_mixer_verify_desc(dp, 0);
48583a3f90c6SAndrew Thompson 
48593a3f90c6SAndrew Thompson 		if (au) {
48603a3f90c6SAndrew Thompson 			iot[au->bUnitId].u.desc = (const void *)au;
4861e2524b2eSHans Petter Selasky 			if (au->bUnitId > ID_max)
48623a3f90c6SAndrew Thompson 				ID_max = au->bUnitId;
48633a3f90c6SAndrew Thompson 		}
48643a3f90c6SAndrew Thompson 	}
48653a3f90c6SAndrew Thompson 
48663a3f90c6SAndrew Thompson 	DPRINTF("Maximum ID=%d\n", ID_max);
48673a3f90c6SAndrew Thompson 
48683a3f90c6SAndrew Thompson 	/*
48693a3f90c6SAndrew Thompson 	 * determine sourcing inputs for
48703a3f90c6SAndrew Thompson 	 * all nodes in the tree:
48713a3f90c6SAndrew Thompson 	 */
48723a3f90c6SAndrew Thompson 	i = ID_max;
48733a3f90c6SAndrew Thompson 	do {
4874e2524b2eSHans Petter Selasky 		if (sc->sc_audio_rev >= UAUDIO_VERSION_30) {
4875e2524b2eSHans Petter Selasky 			/* FALLTHROUGH */
4876e2524b2eSHans Petter Selasky 		} else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) {
4877e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(iot,
4878e2524b2eSHans Petter Selasky 			    &i, 1, &((iot + i)->usr));
4879e2524b2eSHans Petter Selasky 
4880e2524b2eSHans Petter Selasky 			sc->sc_mixer_clocks.is_input = 255;
4881e2524b2eSHans Petter Selasky 			sc->sc_mixer_clocks.recurse_level = 0;
4882e2524b2eSHans Petter Selasky 
4883e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_clocks_sub(iot,
4884e2524b2eSHans Petter Selasky 			    &i, 1, &sc->sc_mixer_clocks);
4885e2524b2eSHans Petter Selasky 		} else {
4886e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(iot,
4887e2524b2eSHans Petter Selasky 			    &i, 1, &((iot + i)->usr));
4888e2524b2eSHans Petter Selasky 		}
48893a3f90c6SAndrew Thompson 	} while (i--);
48903a3f90c6SAndrew Thompson 
48913a3f90c6SAndrew Thompson 	/*
48923a3f90c6SAndrew Thompson 	 * determine outputs for
48933a3f90c6SAndrew Thompson 	 * all nodes in the tree:
48943a3f90c6SAndrew Thompson 	 */
48953a3f90c6SAndrew Thompson 	i = ID_max;
48963a3f90c6SAndrew Thompson 	do {
4897e2524b2eSHans Petter Selasky 		uaudio_mixer_find_outputs_sub(iot,
4898e2524b2eSHans Petter Selasky 		    i, ID_max, &((iot + i)->usr));
48993a3f90c6SAndrew Thompson 	} while (i--);
49003a3f90c6SAndrew Thompson 
49013a3f90c6SAndrew Thompson 	/* set "id_max" and "root" */
49023a3f90c6SAndrew Thompson 
49033a3f90c6SAndrew Thompson 	i = ID_max;
49043a3f90c6SAndrew Thompson 	do {
49053a3f90c6SAndrew Thompson 		(iot + i)->usr.id_max = ID_max;
49063a3f90c6SAndrew Thompson 		(iot + i)->root = iot;
49073a3f90c6SAndrew Thompson 	} while (i--);
49083a3f90c6SAndrew Thompson 
49093a3f90c6SAndrew Thompson 	/*
4910e2524b2eSHans Petter Selasky 	 * Scan the config to create a linked list of "mixer" nodes:
49113a3f90c6SAndrew Thompson 	 */
49123a3f90c6SAndrew Thompson 
49133a3f90c6SAndrew Thompson 	i = ID_max;
49143a3f90c6SAndrew Thompson 	do {
49153a3f90c6SAndrew Thompson 		dp = iot[i].u.desc;
49163a3f90c6SAndrew Thompson 
4917e2524b2eSHans Petter Selasky 		if (dp == NULL)
49183a3f90c6SAndrew Thompson 			continue;
4919e2524b2eSHans Petter Selasky 
49203a3f90c6SAndrew Thompson 		DPRINTFN(11, "id=%d subtype=%d\n",
49213a3f90c6SAndrew Thompson 		    i, dp->bDescriptorSubtype);
49223a3f90c6SAndrew Thompson 
4923e2524b2eSHans Petter Selasky 		if (sc->sc_audio_rev >= UAUDIO_VERSION_30) {
4924e2524b2eSHans Petter Selasky 			continue;
4925e2524b2eSHans Petter Selasky 		} else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) {
4926e2524b2eSHans Petter Selasky 
49273a3f90c6SAndrew Thompson 			switch (dp->bDescriptorSubtype) {
49283a3f90c6SAndrew Thompson 			case UDESCSUB_AC_HEADER:
49293a3f90c6SAndrew Thompson 				DPRINTF("unexpected AC header\n");
49303a3f90c6SAndrew Thompson 				break;
49313a3f90c6SAndrew Thompson 
49323a3f90c6SAndrew Thompson 			case UDESCSUB_AC_INPUT:
4933e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_OUTPUT:
4934e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_PROCESSING_V2:
4935e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_EXTENSION_V2:
4936e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_EFFECT:
4937e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_CLOCK_SRC:
4938e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_CLOCK_SEL:
4939e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_CLOCK_MUL:
4940e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_SAMPLE_RT:
49413a3f90c6SAndrew Thompson 				break;
49423a3f90c6SAndrew Thompson 
4943e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_MIXER:
4944e2524b2eSHans Petter Selasky 				uaudio20_mixer_add_mixer(sc, iot, i);
4945e2524b2eSHans Petter Selasky 				break;
4946e2524b2eSHans Petter Selasky 
4947e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_SELECTOR:
4948e2524b2eSHans Petter Selasky 				uaudio20_mixer_add_selector(sc, iot, i);
4949e2524b2eSHans Petter Selasky 				break;
4950e2524b2eSHans Petter Selasky 
4951e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_FEATURE:
4952e2524b2eSHans Petter Selasky 				uaudio20_mixer_add_feature(sc, iot, i);
4953e2524b2eSHans Petter Selasky 				break;
4954e2524b2eSHans Petter Selasky 
4955e2524b2eSHans Petter Selasky 			default:
4956e2524b2eSHans Petter Selasky 				DPRINTF("bad AC desc subtype=0x%02x\n",
4957e2524b2eSHans Petter Selasky 				    dp->bDescriptorSubtype);
4958e2524b2eSHans Petter Selasky 				break;
4959e2524b2eSHans Petter Selasky 			}
4960e2524b2eSHans Petter Selasky 			continue;
4961e2524b2eSHans Petter Selasky 		}
4962e2524b2eSHans Petter Selasky 
4963e2524b2eSHans Petter Selasky 		switch (dp->bDescriptorSubtype) {
4964e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_HEADER:
4965e2524b2eSHans Petter Selasky 			DPRINTF("unexpected AC header\n");
4966e2524b2eSHans Petter Selasky 			break;
4967e2524b2eSHans Petter Selasky 
4968e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_INPUT:
49693a3f90c6SAndrew Thompson 		case UDESCSUB_AC_OUTPUT:
49703a3f90c6SAndrew Thompson 			break;
49713a3f90c6SAndrew Thompson 
49723a3f90c6SAndrew Thompson 		case UDESCSUB_AC_MIXER:
49733a3f90c6SAndrew Thompson 			uaudio_mixer_add_mixer(sc, iot, i);
49743a3f90c6SAndrew Thompson 			break;
49753a3f90c6SAndrew Thompson 
49763a3f90c6SAndrew Thompson 		case UDESCSUB_AC_SELECTOR:
49773a3f90c6SAndrew Thompson 			uaudio_mixer_add_selector(sc, iot, i);
49783a3f90c6SAndrew Thompson 			break;
49793a3f90c6SAndrew Thompson 
49803a3f90c6SAndrew Thompson 		case UDESCSUB_AC_FEATURE:
49813a3f90c6SAndrew Thompson 			uaudio_mixer_add_feature(sc, iot, i);
49823a3f90c6SAndrew Thompson 			break;
49833a3f90c6SAndrew Thompson 
49843a3f90c6SAndrew Thompson 		case UDESCSUB_AC_PROCESSING:
49853a3f90c6SAndrew Thompson 			uaudio_mixer_add_processing(sc, iot, i);
49863a3f90c6SAndrew Thompson 			break;
49873a3f90c6SAndrew Thompson 
49883a3f90c6SAndrew Thompson 		case UDESCSUB_AC_EXTENSION:
49893a3f90c6SAndrew Thompson 			uaudio_mixer_add_extension(sc, iot, i);
49903a3f90c6SAndrew Thompson 			break;
49913a3f90c6SAndrew Thompson 
49923a3f90c6SAndrew Thompson 		default:
49933a3f90c6SAndrew Thompson 			DPRINTF("bad AC desc subtype=0x%02x\n",
49943a3f90c6SAndrew Thompson 			    dp->bDescriptorSubtype);
49953a3f90c6SAndrew Thompson 			break;
49963a3f90c6SAndrew Thompson 		}
49973a3f90c6SAndrew Thompson 
49983a3f90c6SAndrew Thompson 	} while (i--);
49993a3f90c6SAndrew Thompson 
50003a3f90c6SAndrew Thompson done:
50013a3f90c6SAndrew Thompson 	free(iot, M_TEMP);
50023a3f90c6SAndrew Thompson }
50033a3f90c6SAndrew Thompson 
5004e2524b2eSHans Petter Selasky static int
5005e2524b2eSHans Petter Selasky uaudio_mixer_get(struct usb_device *udev, uint16_t audio_rev,
5006e2524b2eSHans Petter Selasky     uint8_t what, struct uaudio_mixer_node *mc)
50073a3f90c6SAndrew Thompson {
5008760bc48eSAndrew Thompson 	struct usb_device_request req;
5009e2524b2eSHans Petter Selasky 	int val;
5010e2524b2eSHans Petter Selasky 	uint8_t data[2 + (2 * 3)];
5011e0a69b51SAndrew Thompson 	usb_error_t err;
50123a3f90c6SAndrew Thompson 
5013e2524b2eSHans Petter Selasky 	if (mc->wValue[0] == -1)
50143a3f90c6SAndrew Thompson 		return (0);
5015e2524b2eSHans Petter Selasky 
5016e2524b2eSHans Petter Selasky 	if (audio_rev >= UAUDIO_VERSION_30)
5017e2524b2eSHans Petter Selasky 		return (0);
5018e2524b2eSHans Petter Selasky 	else if (audio_rev >= UAUDIO_VERSION_20) {
5019e2524b2eSHans Petter Selasky 		if (what == GET_CUR) {
5020e2524b2eSHans Petter Selasky 			req.bRequest = UA20_CS_CUR;
5021e2524b2eSHans Petter Selasky 			USETW(req.wLength, 2);
5022e2524b2eSHans Petter Selasky 		} else {
5023e2524b2eSHans Petter Selasky 			req.bRequest = UA20_CS_RANGE;
5024e2524b2eSHans Petter Selasky 			USETW(req.wLength, 8);
50253a3f90c6SAndrew Thompson 		}
5026e2524b2eSHans Petter Selasky 	} else {
5027e2524b2eSHans Petter Selasky 		uint16_t len = MIX_SIZE(mc->type);
5028e2524b2eSHans Petter Selasky 
50293a3f90c6SAndrew Thompson 		req.bRequest = what;
5030e2524b2eSHans Petter Selasky 		USETW(req.wLength, len);
5031e2524b2eSHans Petter Selasky 	}
5032e2524b2eSHans Petter Selasky 
5033e2524b2eSHans Petter Selasky 	req.bmRequestType = UT_READ_CLASS_INTERFACE;
50343a3f90c6SAndrew Thompson 	USETW(req.wValue, mc->wValue[0]);
50353a3f90c6SAndrew Thompson 	USETW(req.wIndex, mc->wIndex);
5036e2524b2eSHans Petter Selasky 
5037e2524b2eSHans Petter Selasky 	memset(data, 0, sizeof(data));
50383a3f90c6SAndrew Thompson 
5039f24b6817SAlfred Perlstein 	err = usbd_do_request(udev, NULL, &req, data);
50403a3f90c6SAndrew Thompson 	if (err) {
5041a593f6b8SAndrew Thompson 		DPRINTF("err=%s\n", usbd_errstr(err));
50423a3f90c6SAndrew Thompson 		return (0);
50433a3f90c6SAndrew Thompson 	}
5044e2524b2eSHans Petter Selasky 
5045e2524b2eSHans Petter Selasky 	if (audio_rev >= UAUDIO_VERSION_30) {
5046e2524b2eSHans Petter Selasky 		val = 0;
5047e2524b2eSHans Petter Selasky 	} else if (audio_rev >= UAUDIO_VERSION_20) {
5048e2524b2eSHans Petter Selasky 		switch (what) {
5049e2524b2eSHans Petter Selasky 		case GET_CUR:
50503a3f90c6SAndrew Thompson 			val = (data[0] | (data[1] << 8));
5051e2524b2eSHans Petter Selasky 			break;
5052e2524b2eSHans Petter Selasky 		case GET_MIN:
5053e2524b2eSHans Petter Selasky 			val = (data[2] | (data[3] << 8));
5054e2524b2eSHans Petter Selasky 			break;
5055e2524b2eSHans Petter Selasky 		case GET_MAX:
5056e2524b2eSHans Petter Selasky 			val = (data[4] | (data[5] << 8));
5057e2524b2eSHans Petter Selasky 			break;
5058e2524b2eSHans Petter Selasky 		case GET_RES:
5059e2524b2eSHans Petter Selasky 			val = (data[6] | (data[7] << 8));
5060e2524b2eSHans Petter Selasky 			break;
5061e2524b2eSHans Petter Selasky 		default:
5062e2524b2eSHans Petter Selasky 			val = 0;
5063e2524b2eSHans Petter Selasky 			break;
5064e2524b2eSHans Petter Selasky 		}
5065e2524b2eSHans Petter Selasky 	} else {
5066e2524b2eSHans Petter Selasky 		val = (data[0] | (data[1] << 8));
5067e2524b2eSHans Petter Selasky 	}
5068e2524b2eSHans Petter Selasky 
5069e2524b2eSHans Petter Selasky 	if (what == GET_CUR || what == GET_MIN || what == GET_MAX)
5070e2524b2eSHans Petter Selasky 		val = uaudio_mixer_signext(mc->type, val);
50713a3f90c6SAndrew Thompson 
50723a3f90c6SAndrew Thompson 	DPRINTFN(3, "val=%d\n", val);
50733a3f90c6SAndrew Thompson 
50743a3f90c6SAndrew Thompson 	return (val);
50753a3f90c6SAndrew Thompson }
50763a3f90c6SAndrew Thompson 
50773a3f90c6SAndrew Thompson static void
5078ed6d949aSAndrew Thompson uaudio_mixer_write_cfg_callback(struct usb_xfer *xfer, usb_error_t error)
50793a3f90c6SAndrew Thompson {
5080760bc48eSAndrew Thompson 	struct usb_device_request req;
5081ed6d949aSAndrew Thompson 	struct uaudio_softc *sc = usbd_xfer_softc(xfer);
50823a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *mc = sc->sc_mixer_curr;
5083ed6d949aSAndrew Thompson 	struct usb_page_cache *pc;
50843a3f90c6SAndrew Thompson 	uint16_t len;
50853a3f90c6SAndrew Thompson 	uint8_t repeat = 1;
50863a3f90c6SAndrew Thompson 	uint8_t update;
50873a3f90c6SAndrew Thompson 	uint8_t chan;
50883a3f90c6SAndrew Thompson 	uint8_t buf[2];
50893a3f90c6SAndrew Thompson 
50903a3f90c6SAndrew Thompson 	DPRINTF("\n");
50913a3f90c6SAndrew Thompson 
50923a3f90c6SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
50933a3f90c6SAndrew Thompson 	case USB_ST_TRANSFERRED:
50943a3f90c6SAndrew Thompson tr_transferred:
50953a3f90c6SAndrew Thompson 	case USB_ST_SETUP:
50963a3f90c6SAndrew Thompson tr_setup:
50973a3f90c6SAndrew Thompson 
50983a3f90c6SAndrew Thompson 		if (mc == NULL) {
50993a3f90c6SAndrew Thompson 			mc = sc->sc_mixer_root;
51003a3f90c6SAndrew Thompson 			sc->sc_mixer_curr = mc;
51013a3f90c6SAndrew Thompson 			sc->sc_mixer_chan = 0;
51023a3f90c6SAndrew Thompson 			repeat = 0;
51033a3f90c6SAndrew Thompson 		}
51043a3f90c6SAndrew Thompson 		while (mc) {
51053a3f90c6SAndrew Thompson 			while (sc->sc_mixer_chan < mc->nchan) {
51063a3f90c6SAndrew Thompson 
51073a3f90c6SAndrew Thompson 				chan = sc->sc_mixer_chan;
51083a3f90c6SAndrew Thompson 
51093a3f90c6SAndrew Thompson 				sc->sc_mixer_chan++;
51103a3f90c6SAndrew Thompson 
51113a3f90c6SAndrew Thompson 				update = ((mc->update[chan / 8] & (1 << (chan % 8))) &&
51123a3f90c6SAndrew Thompson 				    (mc->wValue[chan] != -1));
51133a3f90c6SAndrew Thompson 
51143a3f90c6SAndrew Thompson 				mc->update[chan / 8] &= ~(1 << (chan % 8));
51153a3f90c6SAndrew Thompson 
51163a3f90c6SAndrew Thompson 				if (update) {
51173a3f90c6SAndrew Thompson 
51183a3f90c6SAndrew Thompson 					req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
51193a3f90c6SAndrew Thompson 					USETW(req.wValue, mc->wValue[chan]);
51203a3f90c6SAndrew Thompson 					USETW(req.wIndex, mc->wIndex);
51213a3f90c6SAndrew Thompson 
5122e2524b2eSHans Petter Selasky 					if (sc->sc_audio_rev >= UAUDIO_VERSION_30) {
5123e2524b2eSHans Petter Selasky 						return;
5124e2524b2eSHans Petter Selasky 					} else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) {
5125e2524b2eSHans Petter Selasky 						len = 2;
5126e2524b2eSHans Petter Selasky 						req.bRequest = UA20_CS_CUR;
5127e2524b2eSHans Petter Selasky 						USETW(req.wLength, len);
5128e2524b2eSHans Petter Selasky 					} else {
5129e2524b2eSHans Petter Selasky 						len = MIX_SIZE(mc->type);
5130e2524b2eSHans Petter Selasky 						req.bRequest = SET_CUR;
5131e2524b2eSHans Petter Selasky 						USETW(req.wLength, len);
5132e2524b2eSHans Petter Selasky 					}
5133e2524b2eSHans Petter Selasky 
51343a3f90c6SAndrew Thompson 					buf[0] = (mc->wData[chan] & 0xFF);
51353a3f90c6SAndrew Thompson 					buf[1] = (mc->wData[chan] >> 8) & 0xFF;
5136e2524b2eSHans Petter Selasky 
5137ed6d949aSAndrew Thompson 					pc = usbd_xfer_get_frame(xfer, 0);
5138ed6d949aSAndrew Thompson 					usbd_copy_in(pc, 0, &req, sizeof(req));
5139ed6d949aSAndrew Thompson 					pc = usbd_xfer_get_frame(xfer, 1);
5140ed6d949aSAndrew Thompson 					usbd_copy_in(pc, 0, buf, len);
51413a3f90c6SAndrew Thompson 
5142ed6d949aSAndrew Thompson 					usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
5143ed6d949aSAndrew Thompson 					usbd_xfer_set_frame_len(xfer, 1, len);
5144ed6d949aSAndrew Thompson 					usbd_xfer_set_frames(xfer, len ? 2 : 1);
5145a593f6b8SAndrew Thompson 					usbd_transfer_submit(xfer);
51463a3f90c6SAndrew Thompson 					return;
51473a3f90c6SAndrew Thompson 				}
51483a3f90c6SAndrew Thompson 			}
51493a3f90c6SAndrew Thompson 
51503a3f90c6SAndrew Thompson 			mc = mc->next;
51513a3f90c6SAndrew Thompson 			sc->sc_mixer_curr = mc;
51523a3f90c6SAndrew Thompson 			sc->sc_mixer_chan = 0;
51533a3f90c6SAndrew Thompson 		}
51543a3f90c6SAndrew Thompson 
51553a3f90c6SAndrew Thompson 		if (repeat) {
51563a3f90c6SAndrew Thompson 			goto tr_setup;
51573a3f90c6SAndrew Thompson 		}
51583a3f90c6SAndrew Thompson 		break;
51593a3f90c6SAndrew Thompson 
51603a3f90c6SAndrew Thompson 	default:			/* Error */
5161ed6d949aSAndrew Thompson 		DPRINTF("error=%s\n", usbd_errstr(error));
5162ed6d949aSAndrew Thompson 		if (error == USB_ERR_CANCELLED) {
51633a3f90c6SAndrew Thompson 			/* do nothing - we are detaching */
51643a3f90c6SAndrew Thompson 			break;
51653a3f90c6SAndrew Thompson 		}
51663a3f90c6SAndrew Thompson 		goto tr_transferred;
51673a3f90c6SAndrew Thompson 	}
51683a3f90c6SAndrew Thompson }
51693a3f90c6SAndrew Thompson 
5170e0a69b51SAndrew Thompson static usb_error_t
5171760bc48eSAndrew Thompson uaudio_set_speed(struct usb_device *udev, uint8_t endpt, uint32_t speed)
51723a3f90c6SAndrew Thompson {
5173760bc48eSAndrew Thompson 	struct usb_device_request req;
51743a3f90c6SAndrew Thompson 	uint8_t data[3];
51753a3f90c6SAndrew Thompson 
51763a3f90c6SAndrew Thompson 	DPRINTFN(6, "endpt=%d speed=%u\n", endpt, speed);
51773a3f90c6SAndrew Thompson 
51783a3f90c6SAndrew Thompson 	req.bmRequestType = UT_WRITE_CLASS_ENDPOINT;
51793a3f90c6SAndrew Thompson 	req.bRequest = SET_CUR;
51803a3f90c6SAndrew Thompson 	USETW2(req.wValue, SAMPLING_FREQ_CONTROL, 0);
51813a3f90c6SAndrew Thompson 	USETW(req.wIndex, endpt);
51823a3f90c6SAndrew Thompson 	USETW(req.wLength, 3);
51833a3f90c6SAndrew Thompson 	data[0] = speed;
51843a3f90c6SAndrew Thompson 	data[1] = speed >> 8;
51853a3f90c6SAndrew Thompson 	data[2] = speed >> 16;
51863a3f90c6SAndrew Thompson 
5187f24b6817SAlfred Perlstein 	return (usbd_do_request(udev, NULL, &req, data));
51883a3f90c6SAndrew Thompson }
51893a3f90c6SAndrew Thompson 
5190e2524b2eSHans Petter Selasky static usb_error_t
5191e2524b2eSHans Petter Selasky uaudio20_set_speed(struct usb_device *udev, uint8_t iface_no,
5192e2524b2eSHans Petter Selasky     uint8_t clockid, uint32_t speed)
5193e2524b2eSHans Petter Selasky {
5194e2524b2eSHans Petter Selasky 	struct usb_device_request req;
5195e2524b2eSHans Petter Selasky 	uint8_t data[4];
5196e2524b2eSHans Petter Selasky 
5197e2524b2eSHans Petter Selasky 	DPRINTFN(6, "ifaceno=%d clockid=%d speed=%u\n",
5198e2524b2eSHans Petter Selasky 	    iface_no, clockid, speed);
5199e2524b2eSHans Petter Selasky 
5200e2524b2eSHans Petter Selasky 	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
5201e2524b2eSHans Petter Selasky 	req.bRequest = UA20_CS_CUR;
5202e2524b2eSHans Petter Selasky 	USETW2(req.wValue, UA20_CS_SAM_FREQ_CONTROL, 0);
5203e2524b2eSHans Petter Selasky 	USETW2(req.wIndex, clockid, iface_no);
5204e2524b2eSHans Petter Selasky 	USETW(req.wLength, 4);
5205e2524b2eSHans Petter Selasky 	data[0] = speed;
5206e2524b2eSHans Petter Selasky 	data[1] = speed >> 8;
5207e2524b2eSHans Petter Selasky 	data[2] = speed >> 16;
5208e2524b2eSHans Petter Selasky 	data[3] = speed >> 24;
5209e2524b2eSHans Petter Selasky 
5210e2524b2eSHans Petter Selasky 	return (usbd_do_request(udev, NULL, &req, data));
5211e2524b2eSHans Petter Selasky }
5212e2524b2eSHans Petter Selasky 
52133a3f90c6SAndrew Thompson static int
52143a3f90c6SAndrew Thompson uaudio_mixer_signext(uint8_t type, int val)
52153a3f90c6SAndrew Thompson {
52163a3f90c6SAndrew Thompson 	if (!MIX_UNSIGNED(type)) {
52173a3f90c6SAndrew Thompson 		if (MIX_SIZE(type) == 2) {
52183a3f90c6SAndrew Thompson 			val = (int16_t)val;
52193a3f90c6SAndrew Thompson 		} else {
52203a3f90c6SAndrew Thompson 			val = (int8_t)val;
52213a3f90c6SAndrew Thompson 		}
52223a3f90c6SAndrew Thompson 	}
52233a3f90c6SAndrew Thompson 	return (val);
52243a3f90c6SAndrew Thompson }
52253a3f90c6SAndrew Thompson 
52263a3f90c6SAndrew Thompson static int
52273a3f90c6SAndrew Thompson uaudio_mixer_bsd2value(struct uaudio_mixer_node *mc, int32_t val)
52283a3f90c6SAndrew Thompson {
52293a3f90c6SAndrew Thompson 	if (mc->type == MIX_ON_OFF) {
52303a3f90c6SAndrew Thompson 		val = (val != 0);
52313a3f90c6SAndrew Thompson 	} else if (mc->type == MIX_SELECTOR) {
52323a3f90c6SAndrew Thompson 		if ((val < mc->minval) ||
52333a3f90c6SAndrew Thompson 		    (val > mc->maxval)) {
52343a3f90c6SAndrew Thompson 			val = mc->minval;
52353a3f90c6SAndrew Thompson 		}
52363a3f90c6SAndrew Thompson 	} else {
5237b029f6bbSAndrew Thompson 
5238b029f6bbSAndrew Thompson 		/* compute actual volume */
5239b029f6bbSAndrew Thompson 		val = (val * mc->mul) / 255;
5240b029f6bbSAndrew Thompson 
5241b029f6bbSAndrew Thompson 		/* add lower offset */
5242b029f6bbSAndrew Thompson 		val = val + mc->minval;
5243b029f6bbSAndrew Thompson 
5244b029f6bbSAndrew Thompson 		/* make sure we don't write a value out of range */
5245b029f6bbSAndrew Thompson 		if (val > mc->maxval)
5246b029f6bbSAndrew Thompson 			val = mc->maxval;
5247b029f6bbSAndrew Thompson 		else if (val < mc->minval)
5248b029f6bbSAndrew Thompson 			val = mc->minval;
52493a3f90c6SAndrew Thompson 	}
52503a3f90c6SAndrew Thompson 
52513a3f90c6SAndrew Thompson 	DPRINTFN(6, "type=0x%03x val=%d min=%d max=%d val=%d\n",
52523a3f90c6SAndrew Thompson 	    mc->type, val, mc->minval, mc->maxval, val);
52533a3f90c6SAndrew Thompson 	return (val);
52543a3f90c6SAndrew Thompson }
52553a3f90c6SAndrew Thompson 
52563a3f90c6SAndrew Thompson static void
52573a3f90c6SAndrew Thompson uaudio_mixer_ctl_set(struct uaudio_softc *sc, struct uaudio_mixer_node *mc,
52583a3f90c6SAndrew Thompson     uint8_t chan, int32_t val)
52593a3f90c6SAndrew Thompson {
52603a3f90c6SAndrew Thompson 	val = uaudio_mixer_bsd2value(mc, val);
52613a3f90c6SAndrew Thompson 
52623a3f90c6SAndrew Thompson 	mc->update[chan / 8] |= (1 << (chan % 8));
52633a3f90c6SAndrew Thompson 	mc->wData[chan] = val;
52643a3f90c6SAndrew Thompson 
52653a3f90c6SAndrew Thompson 	/* start the transfer, if not already started */
52663a3f90c6SAndrew Thompson 
5267a593f6b8SAndrew Thompson 	usbd_transfer_start(sc->sc_mixer_xfer[0]);
52683a3f90c6SAndrew Thompson }
52693a3f90c6SAndrew Thompson 
52703a3f90c6SAndrew Thompson static void
52713a3f90c6SAndrew Thompson uaudio_mixer_init(struct uaudio_softc *sc)
52723a3f90c6SAndrew Thompson {
52733a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *mc;
52743a3f90c6SAndrew Thompson 	int32_t i;
52753a3f90c6SAndrew Thompson 
52763a3f90c6SAndrew Thompson 	for (mc = sc->sc_mixer_root; mc;
52773a3f90c6SAndrew Thompson 	    mc = mc->next) {
52783a3f90c6SAndrew Thompson 
52793a3f90c6SAndrew Thompson 		if (mc->ctl != SOUND_MIXER_NRDEVICES) {
52803a3f90c6SAndrew Thompson 			/*
52813a3f90c6SAndrew Thompson 			 * Set device mask bits. See
52823a3f90c6SAndrew Thompson 			 * /usr/include/machine/soundcard.h
52833a3f90c6SAndrew Thompson 			 */
52843a3f90c6SAndrew Thompson 			sc->sc_mix_info |= (1 << mc->ctl);
52853a3f90c6SAndrew Thompson 		}
52863a3f90c6SAndrew Thompson 		if ((mc->ctl == SOUND_MIXER_NRDEVICES) &&
52873a3f90c6SAndrew Thompson 		    (mc->type == MIX_SELECTOR)) {
52883a3f90c6SAndrew Thompson 
52893a3f90c6SAndrew Thompson 			for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) {
52903a3f90c6SAndrew Thompson 				if (mc->slctrtype[i - 1] == SOUND_MIXER_NRDEVICES) {
52913a3f90c6SAndrew Thompson 					continue;
52923a3f90c6SAndrew Thompson 				}
52933a3f90c6SAndrew Thompson 				sc->sc_recsrc_info |= 1 << mc->slctrtype[i - 1];
52943a3f90c6SAndrew Thompson 			}
52953a3f90c6SAndrew Thompson 		}
52963a3f90c6SAndrew Thompson 	}
52973a3f90c6SAndrew Thompson }
52983a3f90c6SAndrew Thompson 
52993a3f90c6SAndrew Thompson int
53003a3f90c6SAndrew Thompson uaudio_mixer_init_sub(struct uaudio_softc *sc, struct snd_mixer *m)
53013a3f90c6SAndrew Thompson {
53023a3f90c6SAndrew Thompson 	DPRINTF("\n");
53033a3f90c6SAndrew Thompson 
5304902514f6SHans Petter Selasky 	sc->sc_mixer_lock = mixer_get_lock(m);
530576b71212SHans Petter Selasky 	sc->sc_mixer_dev = m;
5306902514f6SHans Petter Selasky 
5307a593f6b8SAndrew Thompson 	if (usbd_transfer_setup(sc->sc_udev, &sc->sc_mixer_iface_index,
53083a3f90c6SAndrew Thompson 	    sc->sc_mixer_xfer, uaudio_mixer_config, 1, sc,
5309902514f6SHans Petter Selasky 	    sc->sc_mixer_lock)) {
53103a3f90c6SAndrew Thompson 		DPRINTFN(0, "could not allocate USB "
53113a3f90c6SAndrew Thompson 		    "transfer for audio mixer!\n");
53123a3f90c6SAndrew Thompson 		return (ENOMEM);
53133a3f90c6SAndrew Thompson 	}
53143a3f90c6SAndrew Thompson 	if (!(sc->sc_mix_info & SOUND_MASK_VOLUME)) {
53153a3f90c6SAndrew Thompson 		mix_setparentchild(m, SOUND_MIXER_VOLUME, SOUND_MASK_PCM);
53163a3f90c6SAndrew Thompson 		mix_setrealdev(m, SOUND_MIXER_VOLUME, SOUND_MIXER_NONE);
53173a3f90c6SAndrew Thompson 	}
53183a3f90c6SAndrew Thompson 	mix_setdevs(m, sc->sc_mix_info);
53193a3f90c6SAndrew Thompson 	mix_setrecdevs(m, sc->sc_recsrc_info);
53203a3f90c6SAndrew Thompson 	return (0);
53213a3f90c6SAndrew Thompson }
53223a3f90c6SAndrew Thompson 
53233a3f90c6SAndrew Thompson int
53243a3f90c6SAndrew Thompson uaudio_mixer_uninit_sub(struct uaudio_softc *sc)
53253a3f90c6SAndrew Thompson {
53263a3f90c6SAndrew Thompson 	DPRINTF("\n");
53273a3f90c6SAndrew Thompson 
5328a593f6b8SAndrew Thompson 	usbd_transfer_unsetup(sc->sc_mixer_xfer, 1);
53293a3f90c6SAndrew Thompson 
5330902514f6SHans Petter Selasky 	sc->sc_mixer_lock = NULL;
5331902514f6SHans Petter Selasky 
53323a3f90c6SAndrew Thompson 	return (0);
53333a3f90c6SAndrew Thompson }
53343a3f90c6SAndrew Thompson 
53353a3f90c6SAndrew Thompson void
53363a3f90c6SAndrew Thompson uaudio_mixer_set(struct uaudio_softc *sc, unsigned type,
53373a3f90c6SAndrew Thompson     unsigned left, unsigned right)
53383a3f90c6SAndrew Thompson {
53393a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *mc;
5340902514f6SHans Petter Selasky 	int chan;
53413a3f90c6SAndrew Thompson 
5342902514f6SHans Petter Selasky 	for (mc = sc->sc_mixer_root; mc != NULL; mc = mc->next) {
53433a3f90c6SAndrew Thompson 
53443a3f90c6SAndrew Thompson 		if (mc->ctl == type) {
5345902514f6SHans Petter Selasky 			for (chan = 0; chan < mc->nchan; chan++) {
5346902514f6SHans Petter Selasky 				uaudio_mixer_ctl_set(sc, mc, chan,
5347902514f6SHans Petter Selasky 				    (int)((chan == 0 ? left : right) *
5348902514f6SHans Petter Selasky 				    255) / 100);
53493a3f90c6SAndrew Thompson 			}
53503a3f90c6SAndrew Thompson 		}
53513a3f90c6SAndrew Thompson 	}
53523a3f90c6SAndrew Thompson }
53533a3f90c6SAndrew Thompson 
53543a3f90c6SAndrew Thompson uint32_t
53553a3f90c6SAndrew Thompson uaudio_mixer_setrecsrc(struct uaudio_softc *sc, uint32_t src)
53563a3f90c6SAndrew Thompson {
53573a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *mc;
53583a3f90c6SAndrew Thompson 	uint32_t mask;
53593a3f90c6SAndrew Thompson 	uint32_t temp;
53603a3f90c6SAndrew Thompson 	int32_t i;
53613a3f90c6SAndrew Thompson 
53623a3f90c6SAndrew Thompson 	for (mc = sc->sc_mixer_root; mc;
53633a3f90c6SAndrew Thompson 	    mc = mc->next) {
53643a3f90c6SAndrew Thompson 
53653a3f90c6SAndrew Thompson 		if ((mc->ctl == SOUND_MIXER_NRDEVICES) &&
53663a3f90c6SAndrew Thompson 		    (mc->type == MIX_SELECTOR)) {
53673a3f90c6SAndrew Thompson 
53683a3f90c6SAndrew Thompson 			/* compute selector mask */
53693a3f90c6SAndrew Thompson 
53703a3f90c6SAndrew Thompson 			mask = 0;
53713a3f90c6SAndrew Thompson 			for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) {
53723a3f90c6SAndrew Thompson 				mask |= (1 << mc->slctrtype[i - 1]);
53733a3f90c6SAndrew Thompson 			}
53743a3f90c6SAndrew Thompson 
53753a3f90c6SAndrew Thompson 			temp = mask & src;
53763a3f90c6SAndrew Thompson 			if (temp == 0) {
53773a3f90c6SAndrew Thompson 				continue;
53783a3f90c6SAndrew Thompson 			}
53793a3f90c6SAndrew Thompson 			/* find the first set bit */
53803a3f90c6SAndrew Thompson 			temp = (-temp) & temp;
53813a3f90c6SAndrew Thompson 
53823a3f90c6SAndrew Thompson 			/* update "src" */
53833a3f90c6SAndrew Thompson 			src &= ~mask;
53843a3f90c6SAndrew Thompson 			src |= temp;
53853a3f90c6SAndrew Thompson 
53863a3f90c6SAndrew Thompson 			for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) {
53873a3f90c6SAndrew Thompson 				if (temp != (1 << mc->slctrtype[i - 1])) {
53883a3f90c6SAndrew Thompson 					continue;
53893a3f90c6SAndrew Thompson 				}
53903a3f90c6SAndrew Thompson 				uaudio_mixer_ctl_set(sc, mc, 0, i);
53913a3f90c6SAndrew Thompson 				break;
53923a3f90c6SAndrew Thompson 			}
53933a3f90c6SAndrew Thompson 		}
53943a3f90c6SAndrew Thompson 	}
53953a3f90c6SAndrew Thompson 	return (src);
53963a3f90c6SAndrew Thompson }
53973a3f90c6SAndrew Thompson 
53983a3f90c6SAndrew Thompson /*========================================================================*
53993a3f90c6SAndrew Thompson  * MIDI support routines
54003a3f90c6SAndrew Thompson  *========================================================================*/
54013a3f90c6SAndrew Thompson 
54023a3f90c6SAndrew Thompson static void
5403ed6d949aSAndrew Thompson umidi_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
54043a3f90c6SAndrew Thompson {
5405ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usbd_xfer_softc(xfer);
54063a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub;
5407ed6d949aSAndrew Thompson 	struct usb_page_cache *pc;
54086f068a43SHans Petter Selasky 	uint8_t buf[4];
54093a3f90c6SAndrew Thompson 	uint8_t cmd_len;
54103a3f90c6SAndrew Thompson 	uint8_t cn;
54113a3f90c6SAndrew Thompson 	uint16_t pos;
5412ed6d949aSAndrew Thompson 	int actlen;
5413ed6d949aSAndrew Thompson 
5414ed6d949aSAndrew Thompson 	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
54153a3f90c6SAndrew Thompson 
54163a3f90c6SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
54173a3f90c6SAndrew Thompson 	case USB_ST_TRANSFERRED:
54183a3f90c6SAndrew Thompson 
5419ed6d949aSAndrew Thompson 		DPRINTF("actlen=%d bytes\n", actlen);
54203a3f90c6SAndrew Thompson 
54213a3f90c6SAndrew Thompson 		pos = 0;
5422ed6d949aSAndrew Thompson 		pc = usbd_xfer_get_frame(xfer, 0);
54233a3f90c6SAndrew Thompson 
5424ed6d949aSAndrew Thompson 		while (actlen >= 4) {
54253a3f90c6SAndrew Thompson 
54266f068a43SHans Petter Selasky 			/* copy out the MIDI data */
54276f068a43SHans Petter Selasky 			usbd_copy_out(pc, pos, buf, 4);
54286f068a43SHans Petter Selasky 			/* command length */
54296f068a43SHans Petter Selasky 			cmd_len = umidi_cmd_to_len[buf[0] & 0xF];
54306f068a43SHans Petter Selasky 			/* cable number */
54316f068a43SHans Petter Selasky 			cn = buf[0] >> 4;
54326f068a43SHans Petter Selasky 			/*
54336f068a43SHans Petter Selasky 			 * Lookup sub-channel. The index is range
54346f068a43SHans Petter Selasky 			 * checked below.
54356f068a43SHans Petter Selasky 			 */
54363a3f90c6SAndrew Thompson 			sub = &chan->sub[cn];
54373a3f90c6SAndrew Thompson 
54388bf51ab5SHans Petter Selasky 			if ((cmd_len != 0) && (cn < chan->max_emb_jack) &&
54396f068a43SHans Petter Selasky 			    (sub->read_open != 0)) {
54403a3f90c6SAndrew Thompson 
54416f068a43SHans Petter Selasky 				/* Send data to the application */
54426f068a43SHans Petter Selasky 				usb_fifo_put_data_linear(
54436f068a43SHans Petter Selasky 				    sub->fifo.fp[USB_FIFO_RX],
54446f068a43SHans Petter Selasky 				    buf + 1, cmd_len, 1);
54456f068a43SHans Petter Selasky 			}
5446ed6d949aSAndrew Thompson 			actlen -= 4;
54473a3f90c6SAndrew Thompson 			pos += 4;
54483a3f90c6SAndrew Thompson 		}
54493a3f90c6SAndrew Thompson 
54503a3f90c6SAndrew Thompson 	case USB_ST_SETUP:
54513a3f90c6SAndrew Thompson 		DPRINTF("start\n");
54526f068a43SHans Petter Selasky tr_setup:
5453ed6d949aSAndrew Thompson 		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
5454a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
54556f068a43SHans Petter Selasky 		break;
54563a3f90c6SAndrew Thompson 
54573a3f90c6SAndrew Thompson 	default:
5458ed6d949aSAndrew Thompson 		DPRINTF("error=%s\n", usbd_errstr(error));
54593a3f90c6SAndrew Thompson 
5460ed6d949aSAndrew Thompson 		if (error != USB_ERR_CANCELLED) {
54613a3f90c6SAndrew Thompson 			/* try to clear stall first */
54626f068a43SHans Petter Selasky 			usbd_xfer_set_stall(xfer);
54636f068a43SHans Petter Selasky 			goto tr_setup;
54643a3f90c6SAndrew Thompson 		}
54656f068a43SHans Petter Selasky 		break;
54663a3f90c6SAndrew Thompson 	}
54673a3f90c6SAndrew Thompson }
54683a3f90c6SAndrew Thompson 
54693a3f90c6SAndrew Thompson /*
54703a3f90c6SAndrew Thompson  * The following statemachine, that converts MIDI commands to
54713a3f90c6SAndrew Thompson  * USB MIDI packets, derives from Linux's usbmidi.c, which
54723a3f90c6SAndrew Thompson  * was written by "Clemens Ladisch":
54733a3f90c6SAndrew Thompson  *
54743a3f90c6SAndrew Thompson  * Returns:
54753a3f90c6SAndrew Thompson  *    0: No command
54763a3f90c6SAndrew Thompson  * Else: Command is complete
54773a3f90c6SAndrew Thompson  */
54783a3f90c6SAndrew Thompson static uint8_t
54793a3f90c6SAndrew Thompson umidi_convert_to_usb(struct umidi_sub_chan *sub, uint8_t cn, uint8_t b)
54803a3f90c6SAndrew Thompson {
54813a3f90c6SAndrew Thompson 	uint8_t p0 = (cn << 4);
54823a3f90c6SAndrew Thompson 
54833a3f90c6SAndrew Thompson 	if (b >= 0xf8) {
54843a3f90c6SAndrew Thompson 		sub->temp_0[0] = p0 | 0x0f;
54853a3f90c6SAndrew Thompson 		sub->temp_0[1] = b;
54863a3f90c6SAndrew Thompson 		sub->temp_0[2] = 0;
54873a3f90c6SAndrew Thompson 		sub->temp_0[3] = 0;
54883a3f90c6SAndrew Thompson 		sub->temp_cmd = sub->temp_0;
54893a3f90c6SAndrew Thompson 		return (1);
54903a3f90c6SAndrew Thompson 
54913a3f90c6SAndrew Thompson 	} else if (b >= 0xf0) {
54923a3f90c6SAndrew Thompson 		switch (b) {
54933a3f90c6SAndrew Thompson 		case 0xf0:		/* system exclusive begin */
54943a3f90c6SAndrew Thompson 			sub->temp_1[1] = b;
54953a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_SYSEX_1;
54963a3f90c6SAndrew Thompson 			break;
54973a3f90c6SAndrew Thompson 		case 0xf1:		/* MIDI time code */
54983a3f90c6SAndrew Thompson 		case 0xf3:		/* song select */
54993a3f90c6SAndrew Thompson 			sub->temp_1[1] = b;
55003a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_1PARAM;
55013a3f90c6SAndrew Thompson 			break;
55023a3f90c6SAndrew Thompson 		case 0xf2:		/* song position pointer */
55033a3f90c6SAndrew Thompson 			sub->temp_1[1] = b;
55043a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_2PARAM_1;
55053a3f90c6SAndrew Thompson 			break;
55063a3f90c6SAndrew Thompson 		case 0xf4:		/* unknown */
55073a3f90c6SAndrew Thompson 		case 0xf5:		/* unknown */
55083a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_UNKNOWN;
55093a3f90c6SAndrew Thompson 			break;
55103a3f90c6SAndrew Thompson 		case 0xf6:		/* tune request */
55113a3f90c6SAndrew Thompson 			sub->temp_1[0] = p0 | 0x05;
55123a3f90c6SAndrew Thompson 			sub->temp_1[1] = 0xf6;
55133a3f90c6SAndrew Thompson 			sub->temp_1[2] = 0;
55143a3f90c6SAndrew Thompson 			sub->temp_1[3] = 0;
55153a3f90c6SAndrew Thompson 			sub->temp_cmd = sub->temp_1;
55163a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_UNKNOWN;
55173a3f90c6SAndrew Thompson 			return (1);
55183a3f90c6SAndrew Thompson 
55193a3f90c6SAndrew Thompson 		case 0xf7:		/* system exclusive end */
55203a3f90c6SAndrew Thompson 			switch (sub->state) {
55213a3f90c6SAndrew Thompson 			case UMIDI_ST_SYSEX_0:
55223a3f90c6SAndrew Thompson 				sub->temp_1[0] = p0 | 0x05;
55233a3f90c6SAndrew Thompson 				sub->temp_1[1] = 0xf7;
55243a3f90c6SAndrew Thompson 				sub->temp_1[2] = 0;
55253a3f90c6SAndrew Thompson 				sub->temp_1[3] = 0;
55263a3f90c6SAndrew Thompson 				sub->temp_cmd = sub->temp_1;
55273a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_UNKNOWN;
55283a3f90c6SAndrew Thompson 				return (1);
55293a3f90c6SAndrew Thompson 			case UMIDI_ST_SYSEX_1:
55303a3f90c6SAndrew Thompson 				sub->temp_1[0] = p0 | 0x06;
55313a3f90c6SAndrew Thompson 				sub->temp_1[2] = 0xf7;
55323a3f90c6SAndrew Thompson 				sub->temp_1[3] = 0;
55333a3f90c6SAndrew Thompson 				sub->temp_cmd = sub->temp_1;
55343a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_UNKNOWN;
55353a3f90c6SAndrew Thompson 				return (1);
55363a3f90c6SAndrew Thompson 			case UMIDI_ST_SYSEX_2:
55373a3f90c6SAndrew Thompson 				sub->temp_1[0] = p0 | 0x07;
55383a3f90c6SAndrew Thompson 				sub->temp_1[3] = 0xf7;
55393a3f90c6SAndrew Thompson 				sub->temp_cmd = sub->temp_1;
55403a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_UNKNOWN;
55413a3f90c6SAndrew Thompson 				return (1);
55423a3f90c6SAndrew Thompson 			}
55433a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_UNKNOWN;
55443a3f90c6SAndrew Thompson 			break;
55453a3f90c6SAndrew Thompson 		}
55463a3f90c6SAndrew Thompson 	} else if (b >= 0x80) {
55473a3f90c6SAndrew Thompson 		sub->temp_1[1] = b;
55483a3f90c6SAndrew Thompson 		if ((b >= 0xc0) && (b <= 0xdf)) {
55493a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_1PARAM;
55503a3f90c6SAndrew Thompson 		} else {
55513a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_2PARAM_1;
55523a3f90c6SAndrew Thompson 		}
55533a3f90c6SAndrew Thompson 	} else {			/* b < 0x80 */
55543a3f90c6SAndrew Thompson 		switch (sub->state) {
55553a3f90c6SAndrew Thompson 		case UMIDI_ST_1PARAM:
55563a3f90c6SAndrew Thompson 			if (sub->temp_1[1] < 0xf0) {
55573a3f90c6SAndrew Thompson 				p0 |= sub->temp_1[1] >> 4;
55583a3f90c6SAndrew Thompson 			} else {
55593a3f90c6SAndrew Thompson 				p0 |= 0x02;
55603a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_UNKNOWN;
55613a3f90c6SAndrew Thompson 			}
55623a3f90c6SAndrew Thompson 			sub->temp_1[0] = p0;
55633a3f90c6SAndrew Thompson 			sub->temp_1[2] = b;
55643a3f90c6SAndrew Thompson 			sub->temp_1[3] = 0;
55653a3f90c6SAndrew Thompson 			sub->temp_cmd = sub->temp_1;
55663a3f90c6SAndrew Thompson 			return (1);
55673a3f90c6SAndrew Thompson 		case UMIDI_ST_2PARAM_1:
55683a3f90c6SAndrew Thompson 			sub->temp_1[2] = b;
55693a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_2PARAM_2;
55703a3f90c6SAndrew Thompson 			break;
55713a3f90c6SAndrew Thompson 		case UMIDI_ST_2PARAM_2:
55723a3f90c6SAndrew Thompson 			if (sub->temp_1[1] < 0xf0) {
55733a3f90c6SAndrew Thompson 				p0 |= sub->temp_1[1] >> 4;
55743a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_2PARAM_1;
55753a3f90c6SAndrew Thompson 			} else {
55763a3f90c6SAndrew Thompson 				p0 |= 0x03;
55773a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_UNKNOWN;
55783a3f90c6SAndrew Thompson 			}
55793a3f90c6SAndrew Thompson 			sub->temp_1[0] = p0;
55803a3f90c6SAndrew Thompson 			sub->temp_1[3] = b;
55813a3f90c6SAndrew Thompson 			sub->temp_cmd = sub->temp_1;
55823a3f90c6SAndrew Thompson 			return (1);
55833a3f90c6SAndrew Thompson 		case UMIDI_ST_SYSEX_0:
55843a3f90c6SAndrew Thompson 			sub->temp_1[1] = b;
55853a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_SYSEX_1;
55863a3f90c6SAndrew Thompson 			break;
55873a3f90c6SAndrew Thompson 		case UMIDI_ST_SYSEX_1:
55883a3f90c6SAndrew Thompson 			sub->temp_1[2] = b;
55893a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_SYSEX_2;
55903a3f90c6SAndrew Thompson 			break;
55913a3f90c6SAndrew Thompson 		case UMIDI_ST_SYSEX_2:
55923a3f90c6SAndrew Thompson 			sub->temp_1[0] = p0 | 0x04;
55933a3f90c6SAndrew Thompson 			sub->temp_1[3] = b;
55943a3f90c6SAndrew Thompson 			sub->temp_cmd = sub->temp_1;
55953a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_SYSEX_0;
55963a3f90c6SAndrew Thompson 			return (1);
55976f068a43SHans Petter Selasky 		default:
55986f068a43SHans Petter Selasky 			break;
55993a3f90c6SAndrew Thompson 		}
56003a3f90c6SAndrew Thompson 	}
56013a3f90c6SAndrew Thompson 	return (0);
56023a3f90c6SAndrew Thompson }
56033a3f90c6SAndrew Thompson 
56043a3f90c6SAndrew Thompson static void
5605ed6d949aSAndrew Thompson umidi_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
56063a3f90c6SAndrew Thompson {
5607ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usbd_xfer_softc(xfer);
56083a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub;
5609ed6d949aSAndrew Thompson 	struct usb_page_cache *pc;
56103a3f90c6SAndrew Thompson 	uint32_t actlen;
5611910f1dcfSHans Petter Selasky 	uint16_t nframes;
56123a3f90c6SAndrew Thompson 	uint8_t buf;
56133a3f90c6SAndrew Thompson 	uint8_t start_cable;
56143a3f90c6SAndrew Thompson 	uint8_t tr_any;
5615ed6d949aSAndrew Thompson 	int len;
5616ed6d949aSAndrew Thompson 
5617ed6d949aSAndrew Thompson 	usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
56183a3f90c6SAndrew Thompson 
5619910f1dcfSHans Petter Selasky 	/*
5620910f1dcfSHans Petter Selasky 	 * NOTE: Some MIDI devices only accept 4 bytes of data per
5621910f1dcfSHans Petter Selasky 	 * short terminated USB transfer.
5622910f1dcfSHans Petter Selasky 	 */
56233a3f90c6SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
56243a3f90c6SAndrew Thompson 	case USB_ST_TRANSFERRED:
5625ed6d949aSAndrew Thompson 		DPRINTF("actlen=%d bytes\n", len);
56263a3f90c6SAndrew Thompson 
56273a3f90c6SAndrew Thompson 	case USB_ST_SETUP:
56286f068a43SHans Petter Selasky tr_setup:
56293a3f90c6SAndrew Thompson 		DPRINTF("start\n");
56303a3f90c6SAndrew Thompson 
5631910f1dcfSHans Petter Selasky 		nframes = 0;	/* reset */
56323a3f90c6SAndrew Thompson 		start_cable = chan->curr_cable;
56333a3f90c6SAndrew Thompson 		tr_any = 0;
56344944c3a8SHans Petter Selasky 		pc = usbd_xfer_get_frame(xfer, 0);
56353a3f90c6SAndrew Thompson 
56363a3f90c6SAndrew Thompson 		while (1) {
56373a3f90c6SAndrew Thompson 
56383a3f90c6SAndrew Thompson 			/* round robin de-queueing */
56393a3f90c6SAndrew Thompson 
56403a3f90c6SAndrew Thompson 			sub = &chan->sub[chan->curr_cable];
56413a3f90c6SAndrew Thompson 
56423a3f90c6SAndrew Thompson 			if (sub->write_open) {
5643910f1dcfSHans Petter Selasky 				usb_fifo_get_data_linear(sub->fifo.fp[USB_FIFO_TX],
5644910f1dcfSHans Petter Selasky 				    &buf, 1, &actlen, 0);
56453a3f90c6SAndrew Thompson 			} else {
56463a3f90c6SAndrew Thompson 				actlen = 0;
56473a3f90c6SAndrew Thompson 			}
56483a3f90c6SAndrew Thompson 
56493a3f90c6SAndrew Thompson 			if (actlen) {
56503a3f90c6SAndrew Thompson 
56513a3f90c6SAndrew Thompson 				tr_any = 1;
56523a3f90c6SAndrew Thompson 
5653910f1dcfSHans Petter Selasky 				DPRINTF("byte=0x%02x from FIFO %u\n", buf,
5654910f1dcfSHans Petter Selasky 				    (unsigned int)chan->curr_cable);
56553a3f90c6SAndrew Thompson 
56563a3f90c6SAndrew Thompson 				if (umidi_convert_to_usb(sub, chan->curr_cable, buf)) {
56573a3f90c6SAndrew Thompson 
5658910f1dcfSHans Petter Selasky 					DPRINTF("sub=0x%02x 0x%02x 0x%02x 0x%02x\n",
56593a3f90c6SAndrew Thompson 					    sub->temp_cmd[0], sub->temp_cmd[1],
56603a3f90c6SAndrew Thompson 					    sub->temp_cmd[2], sub->temp_cmd[3]);
56613a3f90c6SAndrew Thompson 
56624944c3a8SHans Petter Selasky 					usbd_copy_in(pc, nframes * 4, sub->temp_cmd, 4);
5663910f1dcfSHans Petter Selasky 
5664910f1dcfSHans Petter Selasky 					nframes++;
56654944c3a8SHans Petter Selasky 
56664944c3a8SHans Petter Selasky 					if ((nframes >= UMIDI_TX_FRAMES) || (chan->single_command != 0))
56673a3f90c6SAndrew Thompson 						break;
56683a3f90c6SAndrew Thompson 				} else {
56693a3f90c6SAndrew Thompson 					continue;
56703a3f90c6SAndrew Thompson 				}
56713a3f90c6SAndrew Thompson 			}
5672910f1dcfSHans Petter Selasky 
56733a3f90c6SAndrew Thompson 			chan->curr_cable++;
56748bf51ab5SHans Petter Selasky 			if (chan->curr_cable >= chan->max_emb_jack)
56753a3f90c6SAndrew Thompson 				chan->curr_cable = 0;
5676910f1dcfSHans Petter Selasky 
56773a3f90c6SAndrew Thompson 			if (chan->curr_cable == start_cable) {
5678910f1dcfSHans Petter Selasky 				if (tr_any == 0)
56793a3f90c6SAndrew Thompson 					break;
56803a3f90c6SAndrew Thompson 				tr_any = 0;
56813a3f90c6SAndrew Thompson 			}
56823a3f90c6SAndrew Thompson 		}
56833a3f90c6SAndrew Thompson 
56844944c3a8SHans Petter Selasky 		if (nframes != 0) {
5685910f1dcfSHans Petter Selasky 			DPRINTF("Transferring %d frames\n", (int)nframes);
56864944c3a8SHans Petter Selasky 			usbd_xfer_set_frame_len(xfer, 0, 4 * nframes);
5687a593f6b8SAndrew Thompson 			usbd_transfer_submit(xfer);
56883a3f90c6SAndrew Thompson 		}
56896f068a43SHans Petter Selasky 		break;
56903a3f90c6SAndrew Thompson 
56913a3f90c6SAndrew Thompson 	default:			/* Error */
56923a3f90c6SAndrew Thompson 
5693ed6d949aSAndrew Thompson 		DPRINTF("error=%s\n", usbd_errstr(error));
56943a3f90c6SAndrew Thompson 
5695ed6d949aSAndrew Thompson 		if (error != USB_ERR_CANCELLED) {
56963a3f90c6SAndrew Thompson 			/* try to clear stall first */
56976f068a43SHans Petter Selasky 			usbd_xfer_set_stall(xfer);
56986f068a43SHans Petter Selasky 			goto tr_setup;
56993a3f90c6SAndrew Thompson 		}
57006f068a43SHans Petter Selasky 		break;
57013a3f90c6SAndrew Thompson 	}
57023a3f90c6SAndrew Thompson }
57033a3f90c6SAndrew Thompson 
57043a3f90c6SAndrew Thompson static struct umidi_sub_chan *
5705760bc48eSAndrew Thompson umidi_sub_by_fifo(struct usb_fifo *fifo)
57063a3f90c6SAndrew Thompson {
5707ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
57083a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub;
57093a3f90c6SAndrew Thompson 	uint32_t n;
57103a3f90c6SAndrew Thompson 
57118bf51ab5SHans Petter Selasky 	for (n = 0; n < UMIDI_EMB_JACK_MAX; n++) {
57123a3f90c6SAndrew Thompson 		sub = &chan->sub[n];
57133a3f90c6SAndrew Thompson 		if ((sub->fifo.fp[USB_FIFO_RX] == fifo) ||
57143a3f90c6SAndrew Thompson 		    (sub->fifo.fp[USB_FIFO_TX] == fifo)) {
57153a3f90c6SAndrew Thompson 			return (sub);
57163a3f90c6SAndrew Thompson 		}
57173a3f90c6SAndrew Thompson 	}
57183a3f90c6SAndrew Thompson 
5719760bc48eSAndrew Thompson 	panic("%s:%d cannot find usb_fifo!\n",
57203a3f90c6SAndrew Thompson 	    __FILE__, __LINE__);
57213a3f90c6SAndrew Thompson 
57223a3f90c6SAndrew Thompson 	return (NULL);
57233a3f90c6SAndrew Thompson }
57243a3f90c6SAndrew Thompson 
57253a3f90c6SAndrew Thompson static void
5726760bc48eSAndrew Thompson umidi_start_read(struct usb_fifo *fifo)
57273a3f90c6SAndrew Thompson {
5728ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
57293a3f90c6SAndrew Thompson 
57306f068a43SHans Petter Selasky 	usbd_transfer_start(chan->xfer[UMIDI_RX_TRANSFER]);
57313a3f90c6SAndrew Thompson }
57323a3f90c6SAndrew Thompson 
57333a3f90c6SAndrew Thompson static void
5734760bc48eSAndrew Thompson umidi_stop_read(struct usb_fifo *fifo)
57353a3f90c6SAndrew Thompson {
5736ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
57373a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo);
57383a3f90c6SAndrew Thompson 
57393a3f90c6SAndrew Thompson 	DPRINTF("\n");
57403a3f90c6SAndrew Thompson 
57413a3f90c6SAndrew Thompson 	sub->read_open = 0;
57423a3f90c6SAndrew Thompson 
57433a3f90c6SAndrew Thompson 	if (--(chan->read_open_refcount) == 0) {
57443a3f90c6SAndrew Thompson 		/*
57453a3f90c6SAndrew Thompson 		 * XXX don't stop the read transfer here, hence that causes
57463a3f90c6SAndrew Thompson 		 * problems with some MIDI adapters
57473a3f90c6SAndrew Thompson 		 */
57483a3f90c6SAndrew Thompson 		DPRINTF("(stopping read transfer)\n");
57493a3f90c6SAndrew Thompson 	}
57503a3f90c6SAndrew Thompson }
57513a3f90c6SAndrew Thompson 
57523a3f90c6SAndrew Thompson static void
5753760bc48eSAndrew Thompson umidi_start_write(struct usb_fifo *fifo)
57543a3f90c6SAndrew Thompson {
5755ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
57563a3f90c6SAndrew Thompson 
57576f068a43SHans Petter Selasky 	usbd_transfer_start(chan->xfer[UMIDI_TX_TRANSFER]);
57583a3f90c6SAndrew Thompson }
57593a3f90c6SAndrew Thompson 
57603a3f90c6SAndrew Thompson static void
5761760bc48eSAndrew Thompson umidi_stop_write(struct usb_fifo *fifo)
57623a3f90c6SAndrew Thompson {
5763ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
57643a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo);
57653a3f90c6SAndrew Thompson 
57663a3f90c6SAndrew Thompson 	DPRINTF("\n");
57673a3f90c6SAndrew Thompson 
57683a3f90c6SAndrew Thompson 	sub->write_open = 0;
57693a3f90c6SAndrew Thompson 
57703a3f90c6SAndrew Thompson 	if (--(chan->write_open_refcount) == 0) {
57713a3f90c6SAndrew Thompson 		DPRINTF("(stopping write transfer)\n");
57726f068a43SHans Petter Selasky 		usbd_transfer_stop(chan->xfer[UMIDI_TX_TRANSFER]);
57733a3f90c6SAndrew Thompson 	}
57743a3f90c6SAndrew Thompson }
57753a3f90c6SAndrew Thompson 
57763a3f90c6SAndrew Thompson static int
5777760bc48eSAndrew Thompson umidi_open(struct usb_fifo *fifo, int fflags)
57783a3f90c6SAndrew Thompson {
5779ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
57803a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo);
57813a3f90c6SAndrew Thompson 
57823a3f90c6SAndrew Thompson 	if (fflags & FREAD) {
5783a593f6b8SAndrew Thompson 		if (usb_fifo_alloc_buffer(fifo, 4, (1024 / 4))) {
57843a3f90c6SAndrew Thompson 			return (ENOMEM);
57853a3f90c6SAndrew Thompson 		}
57867e6e6b67SAndrew Thompson 		mtx_lock(&chan->mtx);
57873a3f90c6SAndrew Thompson 		chan->read_open_refcount++;
57883a3f90c6SAndrew Thompson 		sub->read_open = 1;
57897e6e6b67SAndrew Thompson 		mtx_unlock(&chan->mtx);
57903a3f90c6SAndrew Thompson 	}
57913a3f90c6SAndrew Thompson 	if (fflags & FWRITE) {
5792a593f6b8SAndrew Thompson 		if (usb_fifo_alloc_buffer(fifo, 32, (1024 / 32))) {
57933a3f90c6SAndrew Thompson 			return (ENOMEM);
57943a3f90c6SAndrew Thompson 		}
57953a3f90c6SAndrew Thompson 		/* clear stall first */
57967e6e6b67SAndrew Thompson 		mtx_lock(&chan->mtx);
57973a3f90c6SAndrew Thompson 		chan->write_open_refcount++;
57983a3f90c6SAndrew Thompson 		sub->write_open = 1;
57993a3f90c6SAndrew Thompson 
58003a3f90c6SAndrew Thompson 		/* reset */
58013a3f90c6SAndrew Thompson 		sub->state = UMIDI_ST_UNKNOWN;
58027e6e6b67SAndrew Thompson 		mtx_unlock(&chan->mtx);
58033a3f90c6SAndrew Thompson 	}
58043a3f90c6SAndrew Thompson 	return (0);			/* success */
58053a3f90c6SAndrew Thompson }
58063a3f90c6SAndrew Thompson 
58073a3f90c6SAndrew Thompson static void
5808760bc48eSAndrew Thompson umidi_close(struct usb_fifo *fifo, int fflags)
58093a3f90c6SAndrew Thompson {
58103a3f90c6SAndrew Thompson 	if (fflags & FREAD) {
5811a593f6b8SAndrew Thompson 		usb_fifo_free_buffer(fifo);
58123a3f90c6SAndrew Thompson 	}
58133a3f90c6SAndrew Thompson 	if (fflags & FWRITE) {
5814a593f6b8SAndrew Thompson 		usb_fifo_free_buffer(fifo);
58153a3f90c6SAndrew Thompson 	}
58163a3f90c6SAndrew Thompson }
58173a3f90c6SAndrew Thompson 
58183a3f90c6SAndrew Thompson 
58193a3f90c6SAndrew Thompson static int
5820760bc48eSAndrew Thompson umidi_ioctl(struct usb_fifo *fifo, u_long cmd, void *data,
5821ee3e3ff5SAndrew Thompson     int fflags)
58223a3f90c6SAndrew Thompson {
58233a3f90c6SAndrew Thompson 	return (ENODEV);
58243a3f90c6SAndrew Thompson }
58253a3f90c6SAndrew Thompson 
58263a3f90c6SAndrew Thompson static void
58273a3f90c6SAndrew Thompson umidi_init(device_t dev)
58283a3f90c6SAndrew Thompson {
58293a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(dev);
58303a3f90c6SAndrew Thompson 	struct umidi_chan *chan = &sc->sc_midi_chan;
58313a3f90c6SAndrew Thompson 
58323a3f90c6SAndrew Thompson 	mtx_init(&chan->mtx, "umidi lock", NULL, MTX_DEF | MTX_RECURSE);
58333a3f90c6SAndrew Thompson }
58343a3f90c6SAndrew Thompson 
5835760bc48eSAndrew Thompson static struct usb_fifo_methods umidi_fifo_methods = {
58363a3f90c6SAndrew Thompson 	.f_start_read = &umidi_start_read,
58373a3f90c6SAndrew Thompson 	.f_start_write = &umidi_start_write,
58383a3f90c6SAndrew Thompson 	.f_stop_read = &umidi_stop_read,
58393a3f90c6SAndrew Thompson 	.f_stop_write = &umidi_stop_write,
58403a3f90c6SAndrew Thompson 	.f_open = &umidi_open,
58413a3f90c6SAndrew Thompson 	.f_close = &umidi_close,
58423a3f90c6SAndrew Thompson 	.f_ioctl = &umidi_ioctl,
58433a3f90c6SAndrew Thompson 	.basename[0] = "umidi",
58443a3f90c6SAndrew Thompson };
58453a3f90c6SAndrew Thompson 
584625b74dabSHans Petter Selasky static int
58473a3f90c6SAndrew Thompson umidi_probe(device_t dev)
58483a3f90c6SAndrew Thompson {
58493a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(dev);
5850760bc48eSAndrew Thompson 	struct usb_attach_arg *uaa = device_get_ivars(dev);
58513a3f90c6SAndrew Thompson 	struct umidi_chan *chan = &sc->sc_midi_chan;
58523a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub;
58533a3f90c6SAndrew Thompson 	int unit = device_get_unit(dev);
58543a3f90c6SAndrew Thompson 	int error;
58553a3f90c6SAndrew Thompson 	uint32_t n;
58563a3f90c6SAndrew Thompson 
58574944c3a8SHans Petter Selasky 	if (usb_test_quirk(uaa, UQ_SINGLE_CMD_MIDI))
58584944c3a8SHans Petter Selasky 		chan->single_command = 1;
58594944c3a8SHans Petter Selasky 
5860a593f6b8SAndrew Thompson 	if (usbd_set_alt_interface_index(sc->sc_udev, chan->iface_index,
58613a3f90c6SAndrew Thompson 	    chan->iface_alt_index)) {
58623a3f90c6SAndrew Thompson 		DPRINTF("setting of alternate index failed!\n");
58633a3f90c6SAndrew Thompson 		goto detach;
58643a3f90c6SAndrew Thompson 	}
58656f068a43SHans Petter Selasky 	usbd_set_parent_iface(sc->sc_udev, chan->iface_index,
58666f068a43SHans Petter Selasky 	    sc->sc_mixer_iface_index);
58673a3f90c6SAndrew Thompson 
5868a593f6b8SAndrew Thompson 	error = usbd_transfer_setup(uaa->device, &chan->iface_index,
58693a3f90c6SAndrew Thompson 	    chan->xfer, umidi_config, UMIDI_N_TRANSFER,
58703a3f90c6SAndrew Thompson 	    chan, &chan->mtx);
58713a3f90c6SAndrew Thompson 	if (error) {
5872a593f6b8SAndrew Thompson 		DPRINTF("error=%s\n", usbd_errstr(error));
58733a3f90c6SAndrew Thompson 		goto detach;
58743a3f90c6SAndrew Thompson 	}
5875e91fe3a9SHans Petter Selasky 
5876e91fe3a9SHans Petter Selasky 	/*
5877e91fe3a9SHans Petter Selasky 	 * Some USB MIDI device makers couldn't resist using
5878e91fe3a9SHans Petter Selasky 	 * wMaxPacketSize = 4 for RX and TX BULK endpoints, although
5879e91fe3a9SHans Petter Selasky 	 * that size is an unsupported value for FULL speed BULK
5880e91fe3a9SHans Petter Selasky 	 * endpoints. The same applies to some HIGH speed MIDI devices
5881e91fe3a9SHans Petter Selasky 	 * which are using a wMaxPacketSize different from 512 bytes.
5882e91fe3a9SHans Petter Selasky 	 *
5883e91fe3a9SHans Petter Selasky 	 * Refer to section 5.8.3 in USB 2.0 PDF: Cite: "All Host
5884e91fe3a9SHans Petter Selasky 	 * Controllers are required to have support for 8-, 16-, 32-,
5885e91fe3a9SHans Petter Selasky 	 * and 64-byte maximum packet sizes for full-speed bulk
5886e91fe3a9SHans Petter Selasky 	 * endpoints and 512 bytes for high-speed bulk endpoints."
5887e91fe3a9SHans Petter Selasky 	 */
5888e91fe3a9SHans Petter Selasky 	if (usbd_xfer_maxp_was_clamped(chan->xfer[UMIDI_TX_TRANSFER]))
5889e91fe3a9SHans Petter Selasky 		chan->single_command = 1;
5890e91fe3a9SHans Petter Selasky 
5891e91fe3a9SHans Petter Selasky 	if (chan->single_command != 0)
5892e91fe3a9SHans Petter Selasky 		device_printf(dev, "Single command MIDI quirk enabled\n");
5893e91fe3a9SHans Petter Selasky 
58948bf51ab5SHans Petter Selasky 	if ((chan->max_emb_jack == 0) ||
58958bf51ab5SHans Petter Selasky 	    (chan->max_emb_jack > UMIDI_EMB_JACK_MAX)) {
58968bf51ab5SHans Petter Selasky 		chan->max_emb_jack = UMIDI_EMB_JACK_MAX;
58973a3f90c6SAndrew Thompson 	}
58983a3f90c6SAndrew Thompson 
58998bf51ab5SHans Petter Selasky 	for (n = 0; n < chan->max_emb_jack; n++) {
59003a3f90c6SAndrew Thompson 
59013a3f90c6SAndrew Thompson 		sub = &chan->sub[n];
59023a3f90c6SAndrew Thompson 
5903a593f6b8SAndrew Thompson 		error = usb_fifo_attach(sc->sc_udev, chan, &chan->mtx,
59043a3f90c6SAndrew Thompson 		    &umidi_fifo_methods, &sub->fifo, unit, n,
5905ee3e3ff5SAndrew Thompson 		    chan->iface_index,
5906ee3e3ff5SAndrew Thompson 		    UID_ROOT, GID_OPERATOR, 0644);
59073a3f90c6SAndrew Thompson 		if (error) {
59083a3f90c6SAndrew Thompson 			goto detach;
59093a3f90c6SAndrew Thompson 		}
59103a3f90c6SAndrew Thompson 	}
59113a3f90c6SAndrew Thompson 
59123a3f90c6SAndrew Thompson 	mtx_lock(&chan->mtx);
59133a3f90c6SAndrew Thompson 
59143a3f90c6SAndrew Thompson 	/*
59156f068a43SHans Petter Selasky 	 * NOTE: At least one device will not work properly unless the
59166f068a43SHans Petter Selasky 	 * BULK IN pipe is open all the time. This might have to do
59176f068a43SHans Petter Selasky 	 * about that the internal queues of the device overflow if we
59186f068a43SHans Petter Selasky 	 * don't read them regularly.
59193a3f90c6SAndrew Thompson 	 */
59206f068a43SHans Petter Selasky 	usbd_transfer_start(chan->xfer[UMIDI_RX_TRANSFER]);
59213a3f90c6SAndrew Thompson 
59223a3f90c6SAndrew Thompson 	mtx_unlock(&chan->mtx);
59233a3f90c6SAndrew Thompson 
59243a3f90c6SAndrew Thompson 	return (0);			/* success */
59253a3f90c6SAndrew Thompson 
59263a3f90c6SAndrew Thompson detach:
59273a3f90c6SAndrew Thompson 	return (ENXIO);			/* failure */
59283a3f90c6SAndrew Thompson }
59293a3f90c6SAndrew Thompson 
593025b74dabSHans Petter Selasky static int
59313a3f90c6SAndrew Thompson umidi_detach(device_t dev)
59323a3f90c6SAndrew Thompson {
59333a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(dev);
59343a3f90c6SAndrew Thompson 	struct umidi_chan *chan = &sc->sc_midi_chan;
59353a3f90c6SAndrew Thompson 	uint32_t n;
59363a3f90c6SAndrew Thompson 
59378bf51ab5SHans Petter Selasky 	for (n = 0; n < UMIDI_EMB_JACK_MAX; n++)
5938a593f6b8SAndrew Thompson 		usb_fifo_detach(&chan->sub[n].fifo);
59393a3f90c6SAndrew Thompson 
59403a3f90c6SAndrew Thompson 	mtx_lock(&chan->mtx);
59413a3f90c6SAndrew Thompson 
59426f068a43SHans Petter Selasky 	usbd_transfer_stop(chan->xfer[UMIDI_RX_TRANSFER]);
59433a3f90c6SAndrew Thompson 
59443a3f90c6SAndrew Thompson 	mtx_unlock(&chan->mtx);
59453a3f90c6SAndrew Thompson 
5946a593f6b8SAndrew Thompson 	usbd_transfer_unsetup(chan->xfer, UMIDI_N_TRANSFER);
59473a3f90c6SAndrew Thompson 
59483a3f90c6SAndrew Thompson 	mtx_destroy(&chan->mtx);
59493a3f90c6SAndrew Thompson 
59503a3f90c6SAndrew Thompson 	return (0);
59513a3f90c6SAndrew Thompson }
59523a3f90c6SAndrew Thompson 
595376b71212SHans Petter Selasky static void
595476b71212SHans Petter Selasky uaudio_hid_rx_callback(struct usb_xfer *xfer, usb_error_t error)
595576b71212SHans Petter Selasky {
595676b71212SHans Petter Selasky 	struct uaudio_softc *sc = usbd_xfer_softc(xfer);
595776b71212SHans Petter Selasky 	const uint8_t *buffer = usbd_xfer_get_frame_buffer(xfer, 0);
595876b71212SHans Petter Selasky 	struct snd_mixer *m;
595976b71212SHans Petter Selasky 	uint8_t id;
596076b71212SHans Petter Selasky 	int actlen;
596176b71212SHans Petter Selasky 
596276b71212SHans Petter Selasky 	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
596376b71212SHans Petter Selasky 
596476b71212SHans Petter Selasky 	switch (USB_GET_STATE(xfer)) {
596576b71212SHans Petter Selasky 	case USB_ST_TRANSFERRED:
596676b71212SHans Petter Selasky 		DPRINTF("actlen=%d\n", actlen);
596776b71212SHans Petter Selasky 
596876b71212SHans Petter Selasky 		if (actlen != 0 &&
596976b71212SHans Petter Selasky 		    (sc->sc_hid.flags & UAUDIO_HID_HAS_ID)) {
597076b71212SHans Petter Selasky 			id = *buffer;
597176b71212SHans Petter Selasky 			buffer++;
597276b71212SHans Petter Selasky 			actlen--;
597376b71212SHans Petter Selasky 		} else {
597476b71212SHans Petter Selasky 			id = 0;
597576b71212SHans Petter Selasky 		}
597676b71212SHans Petter Selasky 
597776b71212SHans Petter Selasky 		m = sc->sc_mixer_dev;
597876b71212SHans Petter Selasky 
59792ba0f361SHans Petter Selasky 		if ((sc->sc_hid.flags & UAUDIO_HID_HAS_MUTE) &&
59802ba0f361SHans Petter Selasky 		    (sc->sc_hid.mute_id == id) &&
59812ba0f361SHans Petter Selasky 		    hid_get_data(buffer, actlen,
59822ba0f361SHans Petter Selasky 		    &sc->sc_hid.mute_loc)) {
59832ba0f361SHans Petter Selasky 
59842ba0f361SHans Petter Selasky 			DPRINTF("Mute toggle\n");
59852ba0f361SHans Petter Selasky 
59862ba0f361SHans Petter Selasky 			mixer_hwvol_mute_locked(m);
59872ba0f361SHans Petter Selasky 		}
59882ba0f361SHans Petter Selasky 
598976b71212SHans Petter Selasky 		if ((sc->sc_hid.flags & UAUDIO_HID_HAS_VOLUME_UP) &&
599076b71212SHans Petter Selasky 		    (sc->sc_hid.volume_up_id == id) &&
599176b71212SHans Petter Selasky 		    hid_get_data(buffer, actlen,
599276b71212SHans Petter Selasky 		    &sc->sc_hid.volume_up_loc)) {
599376b71212SHans Petter Selasky 
599476b71212SHans Petter Selasky 			DPRINTF("Volume Up\n");
599576b71212SHans Petter Selasky 
59962ba0f361SHans Petter Selasky 			mixer_hwvol_step_locked(m, 1, 1);
599776b71212SHans Petter Selasky 		}
599876b71212SHans Petter Selasky 
599976b71212SHans Petter Selasky 		if ((sc->sc_hid.flags & UAUDIO_HID_HAS_VOLUME_DOWN) &&
600076b71212SHans Petter Selasky 		    (sc->sc_hid.volume_down_id == id) &&
600176b71212SHans Petter Selasky 		    hid_get_data(buffer, actlen,
600276b71212SHans Petter Selasky 		    &sc->sc_hid.volume_down_loc)) {
600376b71212SHans Petter Selasky 
600476b71212SHans Petter Selasky 			DPRINTF("Volume Down\n");
600576b71212SHans Petter Selasky 
60062ba0f361SHans Petter Selasky 			mixer_hwvol_step_locked(m, -1, -1);
600776b71212SHans Petter Selasky 		}
600876b71212SHans Petter Selasky 
600976b71212SHans Petter Selasky 	case USB_ST_SETUP:
601076b71212SHans Petter Selasky tr_setup:
601176b71212SHans Petter Selasky 		/* check if we can put more data into the FIFO */
601276b71212SHans Petter Selasky 		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
601376b71212SHans Petter Selasky 		usbd_transfer_submit(xfer);
601476b71212SHans Petter Selasky 		break;
601576b71212SHans Petter Selasky 
601676b71212SHans Petter Selasky 	default:			/* Error */
6017e5359a3bSHans Petter Selasky 
6018e5359a3bSHans Petter Selasky 		DPRINTF("error=%s\n", usbd_errstr(error));
6019e5359a3bSHans Petter Selasky 
602076b71212SHans Petter Selasky 		if (error != USB_ERR_CANCELLED) {
6021e5359a3bSHans Petter Selasky 			/* try to clear stall first */
602276b71212SHans Petter Selasky 			usbd_xfer_set_stall(xfer);
602376b71212SHans Petter Selasky 			goto tr_setup;
602476b71212SHans Petter Selasky 		}
602576b71212SHans Petter Selasky 		break;
602676b71212SHans Petter Selasky 	}
602776b71212SHans Petter Selasky }
602876b71212SHans Petter Selasky 
602976b71212SHans Petter Selasky static int
603076b71212SHans Petter Selasky uaudio_hid_probe(struct uaudio_softc *sc,
603176b71212SHans Petter Selasky     struct usb_attach_arg *uaa)
603276b71212SHans Petter Selasky {
603376b71212SHans Petter Selasky 	void *d_ptr;
603476b71212SHans Petter Selasky 	uint32_t flags;
603576b71212SHans Petter Selasky 	uint16_t d_len;
603676b71212SHans Petter Selasky 	uint8_t id;
603776b71212SHans Petter Selasky 	int error;
603876b71212SHans Petter Selasky 
603976b71212SHans Petter Selasky 	if (!(sc->sc_hid.flags & UAUDIO_HID_VALID))
604076b71212SHans Petter Selasky 		return (-1);
604176b71212SHans Petter Selasky 
604276b71212SHans Petter Selasky 	if (sc->sc_mixer_lock == NULL)
604376b71212SHans Petter Selasky 		return (-1);
604476b71212SHans Petter Selasky 
604576b71212SHans Petter Selasky 	/* Get HID descriptor */
604676b71212SHans Petter Selasky 	error = usbd_req_get_hid_desc(uaa->device, NULL, &d_ptr,
604776b71212SHans Petter Selasky 	    &d_len, M_TEMP, sc->sc_hid.iface_index);
604876b71212SHans Petter Selasky 
604976b71212SHans Petter Selasky 	if (error) {
605076b71212SHans Petter Selasky 		DPRINTF("error reading report description\n");
605176b71212SHans Petter Selasky 		return (-1);
605276b71212SHans Petter Selasky 	}
605376b71212SHans Petter Selasky 
605476b71212SHans Petter Selasky 	/* check if there is an ID byte */
605576b71212SHans Petter Selasky 	hid_report_size(d_ptr, d_len, hid_input, &id);
605676b71212SHans Petter Selasky 
605776b71212SHans Petter Selasky 	if (id != 0)
605876b71212SHans Petter Selasky 		sc->sc_hid.flags |= UAUDIO_HID_HAS_ID;
605976b71212SHans Petter Selasky 
606076b71212SHans Petter Selasky 	if (hid_locate(d_ptr, d_len,
606176b71212SHans Petter Selasky 	    HID_USAGE2(HUP_CONSUMER, 0xE9 /* Volume Increment */),
606276b71212SHans Petter Selasky 	    hid_input, 0, &sc->sc_hid.volume_up_loc, &flags,
606376b71212SHans Petter Selasky 	    &sc->sc_hid.volume_up_id)) {
606476b71212SHans Petter Selasky 		if (flags & HIO_VARIABLE)
606576b71212SHans Petter Selasky 			sc->sc_hid.flags |= UAUDIO_HID_HAS_VOLUME_UP;
606676b71212SHans Petter Selasky 		DPRINTFN(1, "Found Volume Up key\n");
606776b71212SHans Petter Selasky 	}
606876b71212SHans Petter Selasky 
606976b71212SHans Petter Selasky 	if (hid_locate(d_ptr, d_len,
607076b71212SHans Petter Selasky 	    HID_USAGE2(HUP_CONSUMER, 0xEA /* Volume Decrement */),
607176b71212SHans Petter Selasky 	    hid_input, 0, &sc->sc_hid.volume_down_loc, &flags,
607276b71212SHans Petter Selasky 	    &sc->sc_hid.volume_down_id)) {
607376b71212SHans Petter Selasky 		if (flags & HIO_VARIABLE)
607476b71212SHans Petter Selasky 			sc->sc_hid.flags |= UAUDIO_HID_HAS_VOLUME_DOWN;
607576b71212SHans Petter Selasky 		DPRINTFN(1, "Found Volume Down key\n");
607676b71212SHans Petter Selasky 	}
607776b71212SHans Petter Selasky 
60782ba0f361SHans Petter Selasky 	if (hid_locate(d_ptr, d_len,
60792ba0f361SHans Petter Selasky 	    HID_USAGE2(HUP_CONSUMER, 0xE2 /* Mute */),
60802ba0f361SHans Petter Selasky 	    hid_input, 0, &sc->sc_hid.mute_loc, &flags,
60812ba0f361SHans Petter Selasky 	    &sc->sc_hid.mute_id)) {
60822ba0f361SHans Petter Selasky 		if (flags & HIO_VARIABLE)
60832ba0f361SHans Petter Selasky 			sc->sc_hid.flags |= UAUDIO_HID_HAS_MUTE;
60842ba0f361SHans Petter Selasky 		DPRINTFN(1, "Found Mute key\n");
60852ba0f361SHans Petter Selasky 	}
60862ba0f361SHans Petter Selasky 
608776b71212SHans Petter Selasky 	free(d_ptr, M_TEMP);
608876b71212SHans Petter Selasky 
608976b71212SHans Petter Selasky 	if (!(sc->sc_hid.flags & (UAUDIO_HID_HAS_VOLUME_UP |
60902ba0f361SHans Petter Selasky 	    UAUDIO_HID_HAS_VOLUME_DOWN |
60912ba0f361SHans Petter Selasky 	    UAUDIO_HID_HAS_MUTE))) {
609276b71212SHans Petter Selasky 		DPRINTFN(1, "Did not find any volume related keys\n");
609376b71212SHans Petter Selasky 		return (-1);
609476b71212SHans Petter Selasky 	}
609576b71212SHans Petter Selasky 
609676b71212SHans Petter Selasky 	/* prevent the uhid driver from attaching */
609776b71212SHans Petter Selasky 	usbd_set_parent_iface(uaa->device, sc->sc_hid.iface_index,
609876b71212SHans Petter Selasky 	    sc->sc_mixer_iface_index);
609976b71212SHans Petter Selasky 
610076b71212SHans Petter Selasky 	/* allocate USB transfers */
610176b71212SHans Petter Selasky 	error = usbd_transfer_setup(uaa->device, &sc->sc_hid.iface_index,
610276b71212SHans Petter Selasky 	    sc->sc_hid.xfer, uaudio_hid_config, UAUDIO_HID_N_TRANSFER,
610376b71212SHans Petter Selasky 	    sc, sc->sc_mixer_lock);
610476b71212SHans Petter Selasky 	if (error) {
610576b71212SHans Petter Selasky 		DPRINTF("error=%s\n", usbd_errstr(error));
610676b71212SHans Petter Selasky 		return (-1);
610776b71212SHans Petter Selasky 	}
610876b71212SHans Petter Selasky 	return (0);
610976b71212SHans Petter Selasky }
611076b71212SHans Petter Selasky 
611176b71212SHans Petter Selasky static void
611276b71212SHans Petter Selasky uaudio_hid_detach(struct uaudio_softc *sc)
611376b71212SHans Petter Selasky {
611476b71212SHans Petter Selasky 	usbd_transfer_unsetup(sc->sc_hid.xfer, UAUDIO_HID_N_TRANSFER);
611576b71212SHans Petter Selasky }
611676b71212SHans Petter Selasky 
6117af26e3dfSHans Petter Selasky DRIVER_MODULE_ORDERED(uaudio, uhub, uaudio_driver, uaudio_devclass, NULL, 0, SI_ORDER_ANY);
61183a3f90c6SAndrew Thompson MODULE_DEPEND(uaudio, usb, 1, 1, 1);
61193a3f90c6SAndrew Thompson MODULE_DEPEND(uaudio, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
61203a3f90c6SAndrew Thompson MODULE_VERSION(uaudio, 1);
6121f809f280SWarner Losh USB_PNP_HOST_INFO(uaudio_devs);
6122