xref: /freebsd/sys/dev/sound/usb/uaudio.c (revision 923e0040)
13a3f90c6SAndrew Thompson /*	$NetBSD: uaudio.c,v 1.91 2004/11/05 17:46:14 kent Exp $	*/
23a3f90c6SAndrew Thompson 
33a3f90c6SAndrew Thompson /*-
4b61a5730SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
5718cf2ccSPedro F. Giffuni  *
63a3f90c6SAndrew Thompson  * Copyright (c) 1999 The NetBSD Foundation, Inc.
73a3f90c6SAndrew Thompson  * All rights reserved.
83a3f90c6SAndrew Thompson  *
93a3f90c6SAndrew Thompson  * This code is derived from software contributed to The NetBSD Foundation
103a3f90c6SAndrew Thompson  * by Lennart Augustsson (lennart@augustsson.net) at
113a3f90c6SAndrew Thompson  * Carlstedt Research & Technology.
123a3f90c6SAndrew Thompson  *
133a3f90c6SAndrew Thompson  * Redistribution and use in source and binary forms, with or without
143a3f90c6SAndrew Thompson  * modification, are permitted provided that the following conditions
153a3f90c6SAndrew Thompson  * are met:
163a3f90c6SAndrew Thompson  * 1. Redistributions of source code must retain the above copyright
173a3f90c6SAndrew Thompson  *    notice, this list of conditions and the following disclaimer.
183a3f90c6SAndrew Thompson  * 2. Redistributions in binary form must reproduce the above copyright
193a3f90c6SAndrew Thompson  *    notice, this list of conditions and the following disclaimer in the
203a3f90c6SAndrew Thompson  *    documentation and/or other materials provided with the distribution.
213a3f90c6SAndrew Thompson  *
223a3f90c6SAndrew Thompson  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
233a3f90c6SAndrew Thompson  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
243a3f90c6SAndrew Thompson  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
253a3f90c6SAndrew Thompson  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
263a3f90c6SAndrew Thompson  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
273a3f90c6SAndrew Thompson  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
283a3f90c6SAndrew Thompson  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
293a3f90c6SAndrew Thompson  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
303a3f90c6SAndrew Thompson  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
313a3f90c6SAndrew Thompson  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
323a3f90c6SAndrew Thompson  * POSSIBILITY OF SUCH DAMAGE.
333a3f90c6SAndrew Thompson  */
343a3f90c6SAndrew Thompson 
354b7ec270SMarius Strobl #include <sys/cdefs.h>
363a3f90c6SAndrew Thompson /*
373a3f90c6SAndrew Thompson  * USB audio specs: http://www.usb.org/developers/devclass_docs/audio10.pdf
383a3f90c6SAndrew Thompson  *                  http://www.usb.org/developers/devclass_docs/frmts10.pdf
393a3f90c6SAndrew Thompson  *                  http://www.usb.org/developers/devclass_docs/termt10.pdf
403a3f90c6SAndrew Thompson  */
413a3f90c6SAndrew Thompson 
423a3f90c6SAndrew Thompson /*
433a3f90c6SAndrew Thompson  * Also merged:
443a3f90c6SAndrew Thompson  *  $NetBSD: uaudio.c,v 1.94 2005/01/15 15:19:53 kent Exp $
453a3f90c6SAndrew Thompson  *  $NetBSD: uaudio.c,v 1.95 2005/01/16 06:02:19 dsainty Exp $
463a3f90c6SAndrew Thompson  *  $NetBSD: uaudio.c,v 1.96 2005/01/16 12:46:00 kent Exp $
473a3f90c6SAndrew Thompson  *  $NetBSD: uaudio.c,v 1.97 2005/02/24 08:19:38 martin Exp $
483a3f90c6SAndrew Thompson  */
493a3f90c6SAndrew Thompson 
50ed6d949aSAndrew Thompson #include <sys/stdint.h>
51ed6d949aSAndrew Thompson #include <sys/stddef.h>
52ed6d949aSAndrew Thompson #include <sys/param.h>
53ed6d949aSAndrew Thompson #include <sys/queue.h>
54ed6d949aSAndrew Thompson #include <sys/types.h>
55ed6d949aSAndrew Thompson #include <sys/systm.h>
56ed6d949aSAndrew Thompson #include <sys/kernel.h>
57ed6d949aSAndrew Thompson #include <sys/bus.h>
58ed6d949aSAndrew Thompson #include <sys/module.h>
59ed6d949aSAndrew Thompson #include <sys/lock.h>
60ed6d949aSAndrew Thompson #include <sys/mutex.h>
61ed6d949aSAndrew Thompson #include <sys/condvar.h>
62ed6d949aSAndrew Thompson #include <sys/sysctl.h>
63ed6d949aSAndrew Thompson #include <sys/sx.h>
64ed6d949aSAndrew Thompson #include <sys/unistd.h>
65ed6d949aSAndrew Thompson #include <sys/callout.h>
66ed6d949aSAndrew Thompson #include <sys/malloc.h>
67ed6d949aSAndrew Thompson #include <sys/priv.h>
68ed6d949aSAndrew Thompson 
69eead9017SVladimir Kondratyev #include <dev/hid/hid.h>
70eead9017SVladimir Kondratyev 
713a3f90c6SAndrew Thompson #include "usbdevs.h"
723a3f90c6SAndrew Thompson #include <dev/usb/usb.h>
73ed6d949aSAndrew Thompson #include <dev/usb/usbdi.h>
74ed6d949aSAndrew Thompson #include <dev/usb/usbdi_util.h>
7576b71212SHans Petter Selasky #include <dev/usb/usbhid.h>
76902514f6SHans Petter Selasky #include <dev/usb/usb_request.h>
77455a367fSHans Petter Selasky #include <dev/usb/usb_process.h>
783a3f90c6SAndrew Thompson 
793a3f90c6SAndrew Thompson #define	USB_DEBUG_VAR uaudio_debug
803a3f90c6SAndrew Thompson #include <dev/usb/usb_debug.h>
813a3f90c6SAndrew Thompson 
823a3f90c6SAndrew Thompson #include <dev/usb/quirk/usb_quirk.h>
833a3f90c6SAndrew Thompson 
843a3f90c6SAndrew Thompson #include <sys/reboot.h>			/* for bootverbose */
853a3f90c6SAndrew Thompson 
8690da2b28SAriff Abdullah #ifdef HAVE_KERNEL_OPTION_HEADERS
8790da2b28SAriff Abdullah #include "opt_snd.h"
8890da2b28SAriff Abdullah #endif
8990da2b28SAriff Abdullah 
903a3f90c6SAndrew Thompson #include <dev/sound/pcm/sound.h>
913a3f90c6SAndrew Thompson #include <dev/sound/usb/uaudioreg.h>
923a3f90c6SAndrew Thompson #include <dev/sound/usb/uaudio.h>
933a3f90c6SAndrew Thompson #include "feeder_if.h"
943a3f90c6SAndrew Thompson 
95afbfddd9SAndrew Thompson static int uaudio_default_rate = 0;		/* use rate list */
9642fdcd9fSFlorian Walpen static int uaudio_default_bits = 0;		/* use default sample size */
97afbfddd9SAndrew Thompson static int uaudio_default_channels = 0;		/* use default */
98b2e97edfSFlorian Walpen static int uaudio_buffer_ms = 4;
99f14436adSHans Petter Selasky static bool uaudio_handle_hid = true;
1003a3f90c6SAndrew Thompson 
101f8d2b1f3SPawel Biernacki static SYSCTL_NODE(_hw_usb, OID_AUTO, uaudio, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
102f8d2b1f3SPawel Biernacki     "USB uaudio");
103f14436adSHans Petter Selasky SYSCTL_BOOL(_hw_usb_uaudio, OID_AUTO, handle_hid, CTLFLAG_RWTUN,
104f14436adSHans Petter Selasky     &uaudio_handle_hid, 0, "uaudio handles any HID volume/mute keys, if set");
105af3b2549SHans Petter Selasky SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_rate, CTLFLAG_RWTUN,
1063a3f90c6SAndrew Thompson     &uaudio_default_rate, 0, "uaudio default sample rate");
107af3b2549SHans Petter Selasky SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_bits, CTLFLAG_RWTUN,
1083a3f90c6SAndrew Thompson     &uaudio_default_bits, 0, "uaudio default sample bits");
109af3b2549SHans Petter Selasky SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, default_channels, CTLFLAG_RWTUN,
1103a3f90c6SAndrew Thompson     &uaudio_default_channels, 0, "uaudio default sample channels");
1114d76235fSHans Petter Selasky 
112b2e97edfSFlorian Walpen #define	UAUDIO_BUFFER_MS_MIN	1
113b2e97edfSFlorian Walpen #define	UAUDIO_BUFFER_MS_MAX	8
114b2e97edfSFlorian Walpen 
1154d76235fSHans Petter Selasky static int
uaudio_buffer_ms_sysctl(SYSCTL_HANDLER_ARGS)1164d76235fSHans Petter Selasky uaudio_buffer_ms_sysctl(SYSCTL_HANDLER_ARGS)
1174d76235fSHans Petter Selasky {
1184d76235fSHans Petter Selasky 	int err, val;
1194d76235fSHans Petter Selasky 
1204d76235fSHans Petter Selasky 	val = uaudio_buffer_ms;
1214d76235fSHans Petter Selasky 	err = sysctl_handle_int(oidp, &val, 0, req);
1224d76235fSHans Petter Selasky 
1234d76235fSHans Petter Selasky 	if (err != 0 || req->newptr == NULL || val == uaudio_buffer_ms)
1244d76235fSHans Petter Selasky 		return (err);
1254d76235fSHans Petter Selasky 
126b2e97edfSFlorian Walpen 	if (val > UAUDIO_BUFFER_MS_MAX)
127b2e97edfSFlorian Walpen 		val = UAUDIO_BUFFER_MS_MAX;
128b2e97edfSFlorian Walpen 	else if (val < UAUDIO_BUFFER_MS_MIN)
129b2e97edfSFlorian Walpen 		val = UAUDIO_BUFFER_MS_MIN;
1304d76235fSHans Petter Selasky 
1314d76235fSHans Petter Selasky 	uaudio_buffer_ms = val;
1324d76235fSHans Petter Selasky 
1334d76235fSHans Petter Selasky 	return (0);
1344d76235fSHans Petter Selasky }
135f8d2b1f3SPawel Biernacki SYSCTL_PROC(_hw_usb_uaudio, OID_AUTO, buffer_ms,
136f8d2b1f3SPawel Biernacki     CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, 0, sizeof(int),
137f8d2b1f3SPawel Biernacki     uaudio_buffer_ms_sysctl, "I",
138b2e97edfSFlorian Walpen     "uaudio buffering delay in milliseconds, from 1 to 8");
139f14436adSHans Petter Selasky 
140f14436adSHans Petter Selasky #ifdef USB_DEBUG
141f14436adSHans Petter Selasky static int uaudio_debug;
142f14436adSHans Petter Selasky 
143f14436adSHans Petter Selasky SYSCTL_INT(_hw_usb_uaudio, OID_AUTO, debug, CTLFLAG_RWTUN,
144f14436adSHans Petter Selasky     &uaudio_debug, 0, "uaudio debug level");
145fb8a716dSHans Petter Selasky #else
146fb8a716dSHans Petter Selasky #define	uaudio_debug 0
1473a3f90c6SAndrew Thompson #endif
1483a3f90c6SAndrew Thompson 
149b029f6bbSAndrew Thompson #define	UAUDIO_NFRAMES		64	/* must be factor of 8 due HS-USB */
1503a3f90c6SAndrew Thompson #define	UAUDIO_NCHANBUFS	2	/* number of outstanding request */
151e2524b2eSHans Petter Selasky #define	UAUDIO_RECURSE_LIMIT	255	/* rounds */
15242fdcd9fSFlorian Walpen #define	UAUDIO_BITS_MAX		32	/* maximum sample size in bits */
1539dd12733SHans Petter Selasky #define	UAUDIO_CHANNELS_MAX	MIN(64, AFMT_CHANNEL_MAX)
1549dd12733SHans Petter Selasky #define	UAUDIO_MATRIX_MAX	8	/* channels */
1553a3f90c6SAndrew Thompson 
1563a3f90c6SAndrew Thompson #define	MAKE_WORD(h,l) (((h) << 8) | (l))
1573a3f90c6SAndrew Thompson #define	BIT_TEST(bm,bno) (((bm)[(bno) / 8] >> (7 - ((bno) % 8))) & 1)
158c737a632SAndrew Thompson #define	UAUDIO_MAX_CHAN(x) (x)
159f7e62ad0SHans Petter Selasky #define	MIX(sc) ((sc)->sc_mixer_node)
1603a3f90c6SAndrew Thompson 
161e2524b2eSHans Petter Selasky union uaudio_asid {
162e2524b2eSHans Petter Selasky 	const struct usb_audio_streaming_interface_descriptor *v1;
163e2524b2eSHans Petter Selasky 	const struct usb_audio20_streaming_interface_descriptor *v2;
164e2524b2eSHans Petter Selasky };
165e2524b2eSHans Petter Selasky 
166e2524b2eSHans Petter Selasky union uaudio_asf1d {
167e2524b2eSHans Petter Selasky 	const struct usb_audio_streaming_type1_descriptor *v1;
168e2524b2eSHans Petter Selasky 	const struct usb_audio20_streaming_type1_descriptor *v2;
169e2524b2eSHans Petter Selasky };
170e2524b2eSHans Petter Selasky 
171e2524b2eSHans Petter Selasky union uaudio_sed {
172e2524b2eSHans Petter Selasky 	const struct usb_audio_streaming_endpoint_descriptor *v1;
173e2524b2eSHans Petter Selasky 	const struct usb_audio20_streaming_endpoint_descriptor *v2;
174e2524b2eSHans Petter Selasky };
175e2524b2eSHans Petter Selasky 
1763a3f90c6SAndrew Thompson struct uaudio_mixer_node {
177902514f6SHans Petter Selasky 	const char *name;
178902514f6SHans Petter Selasky 
1793a3f90c6SAndrew Thompson 	int32_t	minval;
1803a3f90c6SAndrew Thompson 	int32_t	maxval;
181902514f6SHans Petter Selasky #define	MIX_MAX_CHAN 16
1823a3f90c6SAndrew Thompson 	int32_t	wValue[MIX_MAX_CHAN];	/* using nchan */
1833a3f90c6SAndrew Thompson 	uint32_t mul;
1843a3f90c6SAndrew Thompson 	uint32_t ctl;
1853a3f90c6SAndrew Thompson 
186902514f6SHans Petter Selasky 	int wData[MIX_MAX_CHAN];	/* using nchan */
1873a3f90c6SAndrew Thompson 	uint16_t wIndex;
1883a3f90c6SAndrew Thompson 
1893a3f90c6SAndrew Thompson 	uint8_t	update[(MIX_MAX_CHAN + 7) / 8];
1903a3f90c6SAndrew Thompson 	uint8_t	nchan;
1913a3f90c6SAndrew Thompson 	uint8_t	type;
1923a3f90c6SAndrew Thompson #define	MIX_ON_OFF	1
1933a3f90c6SAndrew Thompson #define	MIX_SIGNED_16	2
1943a3f90c6SAndrew Thompson #define	MIX_UNSIGNED_16	3
1953a3f90c6SAndrew Thompson #define	MIX_SIGNED_8	4
1963a3f90c6SAndrew Thompson #define	MIX_SELECTOR	5
1973a3f90c6SAndrew Thompson #define	MIX_UNKNOWN     6
1983a3f90c6SAndrew Thompson #define	MIX_SIZE(n) ((((n) == MIX_SIGNED_16) || \
1993a3f90c6SAndrew Thompson 		      ((n) == MIX_UNSIGNED_16)) ? 2 : 1)
2003a3f90c6SAndrew Thompson #define	MIX_UNSIGNED(n) ((n) == MIX_UNSIGNED_16)
2013a3f90c6SAndrew Thompson 
2023a3f90c6SAndrew Thompson #define	MAX_SELECTOR_INPUT_PIN 256
2033a3f90c6SAndrew Thompson 	uint8_t	slctrtype[MAX_SELECTOR_INPUT_PIN];
204ff4d5953SHans Petter Selasky 	uint8_t val_default;
2053a3f90c6SAndrew Thompson 
206902514f6SHans Petter Selasky 	uint8_t desc[64];
207902514f6SHans Petter Selasky 
2083a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *next;
2093a3f90c6SAndrew Thompson };
2103a3f90c6SAndrew Thompson 
211455a367fSHans Petter Selasky struct uaudio_configure_msg {
212455a367fSHans Petter Selasky 	struct usb_proc_msg hdr;
213455a367fSHans Petter Selasky 	struct uaudio_softc *sc;
214455a367fSHans Petter Selasky };
2153a3f90c6SAndrew Thompson 
2162c2752d3SHans Petter Selasky #define	CHAN_MAX_ALT 24
217455a367fSHans Petter Selasky 
218455a367fSHans Petter Selasky struct uaudio_chan_alt {
219e2524b2eSHans Petter Selasky 	union uaudio_asf1d p_asf1d;
220e2524b2eSHans Petter Selasky 	union uaudio_sed p_sed;
2214c21be9bSRebecca Cran 	const usb_endpoint_descriptor_audio_t *p_ed1;
2223a3f90c6SAndrew Thompson 	const struct uaudio_format *p_fmt;
223455a367fSHans Petter Selasky 	const struct usb_config *usb_cfg;
224455a367fSHans Petter Selasky 	uint32_t sample_rate;	/* in Hz */
225455a367fSHans Petter Selasky 	uint16_t sample_size;
226455a367fSHans Petter Selasky 	uint8_t	iface_index;
227455a367fSHans Petter Selasky 	uint8_t	iface_alt_index;
228455a367fSHans Petter Selasky 	uint8_t channels;
229455a367fSHans Petter Selasky };
230455a367fSHans Petter Selasky 
231455a367fSHans Petter Selasky struct uaudio_chan {
232455a367fSHans Petter Selasky 	struct pcmchan_caps pcm_cap;	/* capabilities */
233455a367fSHans Petter Selasky 	struct uaudio_chan_alt usb_alt[CHAN_MAX_ALT];
234455a367fSHans Petter Selasky 	struct snd_dbuf *pcm_buf;
235455a367fSHans Petter Selasky 	struct mtx *pcm_mtx;		/* lock protecting this structure */
236455a367fSHans Petter Selasky 	struct uaudio_softc *priv_sc;
237455a367fSHans Petter Selasky 	struct pcm_channel *pcm_ch;
238455a367fSHans Petter Selasky 	struct usb_xfer *xfer[UAUDIO_NCHANBUFS + 1];
2393a3f90c6SAndrew Thompson 
2403a3f90c6SAndrew Thompson 	uint8_t *buf;			/* pointer to buffer */
2413a3f90c6SAndrew Thompson 	uint8_t *start;			/* upper layer buffer start */
2423a3f90c6SAndrew Thompson 	uint8_t *end;			/* upper layer buffer end */
2433a3f90c6SAndrew Thompson 	uint8_t *cur;			/* current position in upper layer
2443a3f90c6SAndrew Thompson 					 * buffer */
2453a3f90c6SAndrew Thompson 
246b029f6bbSAndrew Thompson 	uint32_t intr_frames;		/* in units */
247afbfddd9SAndrew Thompson 	uint32_t frames_per_second;
248afbfddd9SAndrew Thompson 	uint32_t sample_rem;
249afbfddd9SAndrew Thompson 	uint32_t sample_curr;
250455a367fSHans Petter Selasky 	uint32_t max_buf;
25185bad582SHans Petter Selasky 	int32_t jitter_rem;
25285bad582SHans Petter Selasky 	int32_t jitter_curr;
25385bad582SHans Petter Selasky 
25485bad582SHans Petter Selasky 	int feedback_rate;
255afbfddd9SAndrew Thompson 
2563a3f90c6SAndrew Thompson 	uint32_t pcm_format[2];
2573a3f90c6SAndrew Thompson 
258afbfddd9SAndrew Thompson 	uint16_t bytes_per_frame[2];
259afbfddd9SAndrew Thompson 
26085bad582SHans Petter Selasky 	uint32_t intr_counter;
26185bad582SHans Petter Selasky 	uint32_t running;
26285bad582SHans Petter Selasky 	uint32_t num_alt;
26385bad582SHans Petter Selasky 	uint32_t cur_alt;
26485bad582SHans Petter Selasky 	uint32_t set_alt;
26585bad582SHans Petter Selasky 	uint32_t operation;
266455a367fSHans Petter Selasky #define	CHAN_OP_NONE 0
267455a367fSHans Petter Selasky #define	CHAN_OP_START 1
268455a367fSHans Petter Selasky #define	CHAN_OP_STOP 2
269455a367fSHans Petter Selasky #define	CHAN_OP_DRAIN 3
27086c9b3f3SHans Petter Selasky 
27186c9b3f3SHans Petter Selasky 	uint8_t iface_index;
2723a3f90c6SAndrew Thompson };
2733a3f90c6SAndrew Thompson 
2748bf51ab5SHans Petter Selasky #define	UMIDI_EMB_JACK_MAX   16		/* units */
275e0b17a62SHans Petter Selasky #define	UMIDI_TX_FRAMES	   256		/* units */
276910f1dcfSHans Petter Selasky #define	UMIDI_TX_BUFFER    (UMIDI_TX_FRAMES * 4)	/* bytes */
2773a3f90c6SAndrew Thompson 
2786f068a43SHans Petter Selasky enum {
2796f068a43SHans Petter Selasky 	UMIDI_TX_TRANSFER,
2806f068a43SHans Petter Selasky 	UMIDI_RX_TRANSFER,
2816f068a43SHans Petter Selasky 	UMIDI_N_TRANSFER,
2826f068a43SHans Petter Selasky };
2836f068a43SHans Petter Selasky 
2843a3f90c6SAndrew Thompson struct umidi_sub_chan {
285760bc48eSAndrew Thompson 	struct usb_fifo_sc fifo;
2863a3f90c6SAndrew Thompson 	uint8_t *temp_cmd;
2873a3f90c6SAndrew Thompson 	uint8_t	temp_0[4];
2883a3f90c6SAndrew Thompson 	uint8_t	temp_1[4];
2893a3f90c6SAndrew Thompson 	uint8_t	state;
2903a3f90c6SAndrew Thompson #define	UMIDI_ST_UNKNOWN   0		/* scan for command */
2913a3f90c6SAndrew Thompson #define	UMIDI_ST_1PARAM    1
2923a3f90c6SAndrew Thompson #define	UMIDI_ST_2PARAM_1  2
2933a3f90c6SAndrew Thompson #define	UMIDI_ST_2PARAM_2  3
2943a3f90c6SAndrew Thompson #define	UMIDI_ST_SYSEX_0   4
2953a3f90c6SAndrew Thompson #define	UMIDI_ST_SYSEX_1   5
2963a3f90c6SAndrew Thompson #define	UMIDI_ST_SYSEX_2   6
2973a3f90c6SAndrew Thompson 
2983a3f90c6SAndrew Thompson 	uint8_t	read_open:1;
2993a3f90c6SAndrew Thompson 	uint8_t	write_open:1;
3003a3f90c6SAndrew Thompson 	uint8_t	unused:6;
3013a3f90c6SAndrew Thompson };
3023a3f90c6SAndrew Thompson 
3033a3f90c6SAndrew Thompson struct umidi_chan {
3048bf51ab5SHans Petter Selasky 	struct umidi_sub_chan sub[UMIDI_EMB_JACK_MAX];
3053a3f90c6SAndrew Thompson 	struct mtx mtx;
3063a3f90c6SAndrew Thompson 
307760bc48eSAndrew Thompson 	struct usb_xfer *xfer[UMIDI_N_TRANSFER];
3083a3f90c6SAndrew Thompson 
3093a3f90c6SAndrew Thompson 	uint8_t	iface_index;
3103a3f90c6SAndrew Thompson 	uint8_t	iface_alt_index;
3113a3f90c6SAndrew Thompson 
3123a3f90c6SAndrew Thompson 	uint8_t	read_open_refcount;
3133a3f90c6SAndrew Thompson 	uint8_t	write_open_refcount;
3143a3f90c6SAndrew Thompson 
3153a3f90c6SAndrew Thompson 	uint8_t	curr_cable;
3168bf51ab5SHans Petter Selasky 	uint8_t	max_emb_jack;
3173a3f90c6SAndrew Thompson 	uint8_t	valid;
3184944c3a8SHans Petter Selasky 	uint8_t single_command;
3193a3f90c6SAndrew Thompson };
3203a3f90c6SAndrew Thompson 
321e2524b2eSHans Petter Selasky struct uaudio_search_result {
322e2524b2eSHans Petter Selasky 	uint8_t	bit_input[(256 + 7) / 8];
323e2524b2eSHans Petter Selasky 	uint8_t	bit_output[(256 + 7) / 8];
324e2524b2eSHans Petter Selasky 	uint8_t	recurse_level;
325e2524b2eSHans Petter Selasky 	uint8_t	id_max;
326e2524b2eSHans Petter Selasky 	uint8_t is_input;
327e2524b2eSHans Petter Selasky };
328e2524b2eSHans Petter Selasky 
32976b71212SHans Petter Selasky enum {
33076b71212SHans Petter Selasky 	UAUDIO_HID_RX_TRANSFER,
33176b71212SHans Petter Selasky 	UAUDIO_HID_N_TRANSFER,
33276b71212SHans Petter Selasky };
33376b71212SHans Petter Selasky 
33476b71212SHans Petter Selasky struct uaudio_hid {
33576b71212SHans Petter Selasky 	struct usb_xfer *xfer[UAUDIO_HID_N_TRANSFER];
33676b71212SHans Petter Selasky 	struct hid_location volume_up_loc;
33776b71212SHans Petter Selasky 	struct hid_location volume_down_loc;
3382ba0f361SHans Petter Selasky 	struct hid_location mute_loc;
33976b71212SHans Petter Selasky 	uint32_t flags;
34076b71212SHans Petter Selasky #define	UAUDIO_HID_VALID		0x0001
34176b71212SHans Petter Selasky #define	UAUDIO_HID_HAS_ID		0x0002
34276b71212SHans Petter Selasky #define	UAUDIO_HID_HAS_VOLUME_UP	0x0004
34376b71212SHans Petter Selasky #define	UAUDIO_HID_HAS_VOLUME_DOWN	0x0008
3442ba0f361SHans Petter Selasky #define	UAUDIO_HID_HAS_MUTE		0x0010
34576b71212SHans Petter Selasky 	uint8_t iface_index;
34676b71212SHans Petter Selasky 	uint8_t volume_up_id;
34776b71212SHans Petter Selasky 	uint8_t volume_down_id;
3482ba0f361SHans Petter Selasky 	uint8_t mute_id;
34976b71212SHans Petter Selasky };
35076b71212SHans Petter Selasky 
351067e471aSHans Petter Selasky #define	UAUDIO_SPDIF_OUT	0x01	/* Enable S/PDIF output */
352067e471aSHans Petter Selasky #define	UAUDIO_SPDIF_OUT_48K	0x02	/* Out sample rate = 48K */
353067e471aSHans Petter Selasky #define	UAUDIO_SPDIF_OUT_96K	0x04	/* Out sample rate = 96K */
354067e471aSHans Petter Selasky #define	UAUDIO_SPDIF_IN_MIX	0x10	/* Input mix enable */
355067e471aSHans Petter Selasky 
35686c9b3f3SHans Petter Selasky #define	UAUDIO_MAX_CHILD 2
35786c9b3f3SHans Petter Selasky 
35886c9b3f3SHans Petter Selasky struct uaudio_softc_child {
35986c9b3f3SHans Petter Selasky 	device_t pcm_device;
36086c9b3f3SHans Petter Selasky 	struct mtx *mixer_lock;
36186c9b3f3SHans Petter Selasky 	struct snd_mixer *mixer_dev;
36286c9b3f3SHans Petter Selasky 
36386c9b3f3SHans Petter Selasky 	uint32_t mix_info;
36486c9b3f3SHans Petter Selasky 	uint32_t recsrc_info;
36586c9b3f3SHans Petter Selasky 
36686c9b3f3SHans Petter Selasky 	uint8_t	pcm_registered:1;
36786c9b3f3SHans Petter Selasky 	uint8_t	mixer_init:1;
36886c9b3f3SHans Petter Selasky };
36986c9b3f3SHans Petter Selasky 
3703a3f90c6SAndrew Thompson struct uaudio_softc {
3713a3f90c6SAndrew Thompson 	struct sbuf sc_sndstat;
3723a3f90c6SAndrew Thompson 	struct sndcard_func sc_sndcard_func;
37386c9b3f3SHans Petter Selasky 	struct uaudio_chan sc_rec_chan[UAUDIO_MAX_CHILD];
37486c9b3f3SHans Petter Selasky 	struct uaudio_chan sc_play_chan[UAUDIO_MAX_CHILD];
3753a3f90c6SAndrew Thompson 	struct umidi_chan sc_midi_chan;
37676b71212SHans Petter Selasky 	struct uaudio_hid sc_hid;
377e2524b2eSHans Petter Selasky 	struct uaudio_search_result sc_mixer_clocks;
378f7e62ad0SHans Petter Selasky 	struct uaudio_mixer_node sc_mixer_node;
379455a367fSHans Petter Selasky 	struct uaudio_configure_msg sc_config_msg[2];
38086c9b3f3SHans Petter Selasky 	struct uaudio_softc_child sc_child[UAUDIO_MAX_CHILD];
3813a3f90c6SAndrew Thompson 
382760bc48eSAndrew Thompson 	struct usb_device *sc_udev;
383760bc48eSAndrew Thompson 	struct usb_xfer *sc_mixer_xfer[1];
3843a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *sc_mixer_root;
3853a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *sc_mixer_curr;
386067e471aSHans Petter Selasky 	int     (*sc_set_spdif_fn) (struct uaudio_softc *, int);
3873a3f90c6SAndrew Thompson 
3883a3f90c6SAndrew Thompson 	uint16_t sc_audio_rev;
3893a3f90c6SAndrew Thompson 	uint16_t sc_mixer_count;
3903a3f90c6SAndrew Thompson 
3913a3f90c6SAndrew Thompson 	uint8_t	sc_mixer_iface_index;
3923a3f90c6SAndrew Thompson 	uint8_t	sc_mixer_iface_no;
3933a3f90c6SAndrew Thompson 	uint8_t	sc_mixer_chan;
39486c9b3f3SHans Petter Selasky 	uint8_t	sc_sndstat_valid:1;
3953a3f90c6SAndrew Thompson 	uint8_t	sc_uq_audio_swap_lr:1;
3963a3f90c6SAndrew Thompson 	uint8_t	sc_uq_au_inp_async:1;
3973a3f90c6SAndrew Thompson 	uint8_t	sc_uq_au_no_xu:1;
3983a3f90c6SAndrew Thompson 	uint8_t	sc_uq_bad_adc:1;
39925b74dabSHans Petter Selasky 	uint8_t	sc_uq_au_vendor_class:1;
4009dd12733SHans Petter Selasky 	uint8_t	sc_pcm_bitperfect:1;
4013a3f90c6SAndrew Thompson };
4023a3f90c6SAndrew Thompson 
4033a3f90c6SAndrew Thompson struct uaudio_terminal_node {
4043a3f90c6SAndrew Thompson 	union {
405760bc48eSAndrew Thompson 		const struct usb_descriptor *desc;
406e2524b2eSHans Petter Selasky 		const struct usb_audio_input_terminal *it_v1;
407e2524b2eSHans Petter Selasky 		const struct usb_audio_output_terminal *ot_v1;
408e2524b2eSHans Petter Selasky 		const struct usb_audio_mixer_unit_0 *mu_v1;
409e2524b2eSHans Petter Selasky 		const struct usb_audio_selector_unit *su_v1;
410e2524b2eSHans Petter Selasky 		const struct usb_audio_feature_unit *fu_v1;
411e2524b2eSHans Petter Selasky 		const struct usb_audio_processing_unit_0 *pu_v1;
412e2524b2eSHans Petter Selasky 		const struct usb_audio_extension_unit_0 *eu_v1;
413e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_source_unit *csrc_v2;
414e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_selector_unit_0 *csel_v2;
415e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_multiplier_unit *cmul_v2;
416e2524b2eSHans Petter Selasky 		const struct usb_audio20_input_terminal *it_v2;
417e2524b2eSHans Petter Selasky 		const struct usb_audio20_output_terminal *ot_v2;
418e2524b2eSHans Petter Selasky 		const struct usb_audio20_mixer_unit_0 *mu_v2;
419e2524b2eSHans Petter Selasky 		const struct usb_audio20_selector_unit *su_v2;
420e2524b2eSHans Petter Selasky 		const struct usb_audio20_feature_unit *fu_v2;
421e2524b2eSHans Petter Selasky 		const struct usb_audio20_sample_rate_unit *ru_v2;
422e2524b2eSHans Petter Selasky 		const struct usb_audio20_processing_unit_0 *pu_v2;
423e2524b2eSHans Petter Selasky 		const struct usb_audio20_extension_unit_0 *eu_v2;
424e2524b2eSHans Petter Selasky 		const struct usb_audio20_effect_unit *ef_v2;
4253a3f90c6SAndrew Thompson 	}	u;
4263a3f90c6SAndrew Thompson 	struct uaudio_search_result usr;
4273a3f90c6SAndrew Thompson 	struct uaudio_terminal_node *root;
4283a3f90c6SAndrew Thompson };
4293a3f90c6SAndrew Thompson 
4303a3f90c6SAndrew Thompson struct uaudio_format {
4313a3f90c6SAndrew Thompson 	uint16_t wFormat;
4323a3f90c6SAndrew Thompson 	uint8_t	bPrecision;
4333a3f90c6SAndrew Thompson 	uint32_t freebsd_fmt;
4343a3f90c6SAndrew Thompson 	const char *description;
4353a3f90c6SAndrew Thompson };
4363a3f90c6SAndrew Thompson 
437e2524b2eSHans Petter Selasky static const struct uaudio_format uaudio10_formats[] = {
4383a3f90c6SAndrew Thompson 	{UA_FMT_PCM8, 8, AFMT_U8, "8-bit U-LE PCM"},
4393a3f90c6SAndrew Thompson 	{UA_FMT_PCM8, 16, AFMT_U16_LE, "16-bit U-LE PCM"},
4403a3f90c6SAndrew Thompson 	{UA_FMT_PCM8, 24, AFMT_U24_LE, "24-bit U-LE PCM"},
4413a3f90c6SAndrew Thompson 	{UA_FMT_PCM8, 32, AFMT_U32_LE, "32-bit U-LE PCM"},
4423a3f90c6SAndrew Thompson 
4433a3f90c6SAndrew Thompson 	{UA_FMT_PCM, 8, AFMT_S8, "8-bit S-LE PCM"},
4443a3f90c6SAndrew Thompson 	{UA_FMT_PCM, 16, AFMT_S16_LE, "16-bit S-LE PCM"},
4453a3f90c6SAndrew Thompson 	{UA_FMT_PCM, 24, AFMT_S24_LE, "24-bit S-LE PCM"},
4463a3f90c6SAndrew Thompson 	{UA_FMT_PCM, 32, AFMT_S32_LE, "32-bit S-LE PCM"},
4473a3f90c6SAndrew Thompson 
4483a3f90c6SAndrew Thompson 	{UA_FMT_ALAW, 8, AFMT_A_LAW, "8-bit A-Law"},
4493a3f90c6SAndrew Thompson 	{UA_FMT_MULAW, 8, AFMT_MU_LAW, "8-bit mu-Law"},
4503a3f90c6SAndrew Thompson 	{0, 0, 0, NULL}
4513a3f90c6SAndrew Thompson };
4523a3f90c6SAndrew Thompson 
453e2524b2eSHans Petter Selasky static const struct uaudio_format uaudio20_formats[] = {
454e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM, 8, AFMT_S8, "8-bit S-LE PCM"},
455e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM, 16, AFMT_S16_LE, "16-bit S-LE PCM"},
456e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM, 24, AFMT_S24_LE, "24-bit S-LE PCM"},
457e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM, 32, AFMT_S32_LE, "32-bit S-LE PCM"},
458e2524b2eSHans Petter Selasky 
459e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM8, 8, AFMT_U8, "8-bit U-LE PCM"},
460e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM8, 16, AFMT_U16_LE, "16-bit U-LE PCM"},
461e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM8, 24, AFMT_U24_LE, "24-bit U-LE PCM"},
462e2524b2eSHans Petter Selasky 	{UA20_FMT_PCM8, 32, AFMT_U32_LE, "32-bit U-LE PCM"},
463e2524b2eSHans Petter Selasky 
464e2524b2eSHans Petter Selasky 	{UA20_FMT_ALAW, 8, AFMT_A_LAW, "8-bit A-Law"},
465e2524b2eSHans Petter Selasky 	{UA20_FMT_MULAW, 8, AFMT_MU_LAW, "8-bit mu-Law"},
466e2524b2eSHans Petter Selasky 	{0, 0, 0, NULL}
467e2524b2eSHans Petter Selasky };
468e2524b2eSHans Petter Selasky 
4693a3f90c6SAndrew Thompson /* prototypes */
4703a3f90c6SAndrew Thompson 
4713a3f90c6SAndrew Thompson static device_probe_t uaudio_probe;
4723a3f90c6SAndrew Thompson static device_attach_t uaudio_attach;
4733a3f90c6SAndrew Thompson static device_detach_t uaudio_detach;
4743a3f90c6SAndrew Thompson 
475e0a69b51SAndrew Thompson static usb_callback_t uaudio_chan_play_callback;
476b4380da7SHans Petter Selasky static usb_callback_t uaudio_chan_play_sync_callback;
477e0a69b51SAndrew Thompson static usb_callback_t uaudio_chan_record_callback;
478b4380da7SHans Petter Selasky static usb_callback_t uaudio_chan_record_sync_callback;
479e0a69b51SAndrew Thompson static usb_callback_t uaudio_mixer_write_cfg_callback;
480e0a69b51SAndrew Thompson static usb_callback_t umidi_bulk_read_callback;
481e0a69b51SAndrew Thompson static usb_callback_t umidi_bulk_write_callback;
48276b71212SHans Petter Selasky static usb_callback_t uaudio_hid_rx_callback;
4833a3f90c6SAndrew Thompson 
484455a367fSHans Petter Selasky static usb_proc_callback_t uaudio_configure_msg;
485455a367fSHans Petter Selasky 
486902514f6SHans Petter Selasky /* ==== USB mixer ==== */
487902514f6SHans Petter Selasky 
488902514f6SHans Petter Selasky static int uaudio_mixer_sysctl_handler(SYSCTL_HANDLER_ARGS);
489902514f6SHans Petter Selasky static void uaudio_mixer_ctl_free(struct uaudio_softc *);
49086c9b3f3SHans Petter Selasky static void uaudio_mixer_register_sysctl(struct uaudio_softc *, device_t, unsigned);
491902514f6SHans Petter Selasky static void uaudio_mixer_reload_all(struct uaudio_softc *);
492ff4d5953SHans Petter Selasky static void uaudio_mixer_controls_create_ftu(struct uaudio_softc *);
493902514f6SHans Petter Selasky 
494e2524b2eSHans Petter Selasky /* ==== USB audio v1.0 ==== */
495e2524b2eSHans Petter Selasky 
4963a3f90c6SAndrew Thompson static void	uaudio_mixer_add_mixer(struct uaudio_softc *,
4973a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
4983a3f90c6SAndrew Thompson static void	uaudio_mixer_add_selector(struct uaudio_softc *,
4993a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
5003a3f90c6SAndrew Thompson static uint32_t	uaudio_mixer_feature_get_bmaControls(
5014c21be9bSRebecca Cran 		    const struct usb_audio_feature_unit *, uint8_t);
5023a3f90c6SAndrew Thompson static void	uaudio_mixer_add_feature(struct uaudio_softc *,
5033a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
5043a3f90c6SAndrew Thompson static void	uaudio_mixer_add_processing_updown(struct uaudio_softc *,
5053a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
5063a3f90c6SAndrew Thompson static void	uaudio_mixer_add_processing(struct uaudio_softc *,
5073a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
5083a3f90c6SAndrew Thompson static void	uaudio_mixer_add_extension(struct uaudio_softc *,
5093a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *, int);
5104c21be9bSRebecca Cran static struct	usb_audio_cluster uaudio_mixer_get_cluster(uint8_t,
5113a3f90c6SAndrew Thompson 		    const struct uaudio_terminal_node *);
512c2bc9ff7SHans Petter Selasky static uint16_t	uaudio_mixer_determine_class(const struct uaudio_terminal_node *);
513e2524b2eSHans Petter Selasky static void	uaudio_mixer_find_inputs_sub(struct uaudio_terminal_node *,
514e2524b2eSHans Petter Selasky 		    const uint8_t *, uint8_t, struct uaudio_search_result *);
515e2524b2eSHans Petter Selasky static const void *uaudio_mixer_verify_desc(const void *, uint32_t);
516e2524b2eSHans Petter Selasky static usb_error_t uaudio_set_speed(struct usb_device *, uint8_t, uint32_t);
517e2524b2eSHans Petter Selasky static int	uaudio_mixer_get(struct usb_device *, uint16_t, uint8_t,
518e2524b2eSHans Petter Selasky 		    struct uaudio_mixer_node *);
519e2524b2eSHans Petter Selasky 
520e2524b2eSHans Petter Selasky /* ==== USB audio v2.0 ==== */
521e2524b2eSHans Petter Selasky 
522e2524b2eSHans Petter Selasky static void	uaudio20_mixer_add_mixer(struct uaudio_softc *,
523e2524b2eSHans Petter Selasky 		    const struct uaudio_terminal_node *, int);
524e2524b2eSHans Petter Selasky static void	uaudio20_mixer_add_selector(struct uaudio_softc *,
525e2524b2eSHans Petter Selasky 		    const struct uaudio_terminal_node *, int);
526e2524b2eSHans Petter Selasky static void	uaudio20_mixer_add_feature(struct uaudio_softc *,
527e2524b2eSHans Petter Selasky 		    const struct uaudio_terminal_node *, int);
528e2524b2eSHans Petter Selasky static struct	usb_audio20_cluster uaudio20_mixer_get_cluster(uint8_t,
529e2524b2eSHans Petter Selasky 		    const struct uaudio_terminal_node *);
530c2bc9ff7SHans Petter Selasky static uint16_t	uaudio20_mixer_determine_class(const struct uaudio_terminal_node *);
531e2524b2eSHans Petter Selasky static void	uaudio20_mixer_find_inputs_sub(struct uaudio_terminal_node *,
532e2524b2eSHans Petter Selasky 		    const uint8_t *, uint8_t, struct uaudio_search_result *);
533e2524b2eSHans Petter Selasky static const void *uaudio20_mixer_verify_desc(const void *, uint32_t);
534e2524b2eSHans Petter Selasky static usb_error_t uaudio20_set_speed(struct usb_device *, uint8_t,
535e2524b2eSHans Petter Selasky 		    uint8_t, uint32_t);
536e2524b2eSHans Petter Selasky 
537e2524b2eSHans Petter Selasky /* USB audio v1.0 and v2.0 */
538e2524b2eSHans Petter Selasky 
539e2524b2eSHans Petter Selasky static void	uaudio_chan_fill_info_sub(struct uaudio_softc *,
540e2524b2eSHans Petter Selasky 		    struct usb_device *, uint32_t, uint8_t, uint8_t);
541e2524b2eSHans Petter Selasky static void	uaudio_chan_fill_info(struct uaudio_softc *,
542e2524b2eSHans Petter Selasky 		    struct usb_device *);
543e2524b2eSHans Petter Selasky static void	uaudio_mixer_add_ctl_sub(struct uaudio_softc *,
544e2524b2eSHans Petter Selasky 		    struct uaudio_mixer_node *);
545e2524b2eSHans Petter Selasky static void	uaudio_mixer_add_ctl(struct uaudio_softc *,
546e2524b2eSHans Petter Selasky 		    struct uaudio_mixer_node *);
547e2524b2eSHans Petter Selasky static void	uaudio_mixer_fill_info(struct uaudio_softc *,
548e2524b2eSHans Petter Selasky 		    struct usb_device *, void *);
549e2524b2eSHans Petter Selasky static int	uaudio_mixer_signext(uint8_t, int);
55086c9b3f3SHans Petter Selasky static void	uaudio_mixer_init(struct uaudio_softc *, unsigned);
5513a3f90c6SAndrew Thompson static uint8_t	umidi_convert_to_usb(struct umidi_sub_chan *, uint8_t, uint8_t);
552760bc48eSAndrew Thompson static struct	umidi_sub_chan *umidi_sub_by_fifo(struct usb_fifo *);
553760bc48eSAndrew Thompson static void	umidi_start_read(struct usb_fifo *);
554760bc48eSAndrew Thompson static void	umidi_stop_read(struct usb_fifo *);
555760bc48eSAndrew Thompson static void	umidi_start_write(struct usb_fifo *);
556760bc48eSAndrew Thompson static void	umidi_stop_write(struct usb_fifo *);
557760bc48eSAndrew Thompson static int	umidi_open(struct usb_fifo *, int);
558760bc48eSAndrew Thompson static int	umidi_ioctl(struct usb_fifo *, u_long cmd, void *, int);
559760bc48eSAndrew Thompson static void	umidi_close(struct usb_fifo *, int);
5603a3f90c6SAndrew Thompson static void	umidi_init(device_t dev);
56125b74dabSHans Petter Selasky static int	umidi_probe(device_t dev);
56225b74dabSHans Petter Selasky static int	umidi_detach(device_t dev);
56376b71212SHans Petter Selasky static int	uaudio_hid_probe(struct uaudio_softc *sc,
56476b71212SHans Petter Selasky 		    struct usb_attach_arg *uaa);
56576b71212SHans Petter Selasky static void	uaudio_hid_detach(struct uaudio_softc *sc);
5663a3f90c6SAndrew Thompson 
567b850ecc1SAndrew Thompson #ifdef USB_DEBUG
5683a3f90c6SAndrew Thompson static void	uaudio_chan_dump_ep_desc(
5694c21be9bSRebecca Cran 		    const usb_endpoint_descriptor_audio_t *);
5703a3f90c6SAndrew Thompson #endif
5713a3f90c6SAndrew Thompson 
572760bc48eSAndrew Thompson static const struct usb_config
573b4380da7SHans Petter Selasky 	uaudio_cfg_record[UAUDIO_NCHANBUFS + 1] = {
5743a3f90c6SAndrew Thompson 	[0] = {
5753a3f90c6SAndrew Thompson 		.type = UE_ISOCHRONOUS,
5763a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
5773a3f90c6SAndrew Thompson 		.direction = UE_DIR_IN,
5784eae601eSAndrew Thompson 		.bufsize = 0,	/* use "wMaxPacketSize * frames" */
579b029f6bbSAndrew Thompson 		.frames = UAUDIO_NFRAMES,
5804eae601eSAndrew Thompson 		.flags = {.short_xfer_ok = 1,},
5814eae601eSAndrew Thompson 		.callback = &uaudio_chan_record_callback,
5823a3f90c6SAndrew Thompson 	},
5833a3f90c6SAndrew Thompson 
5843a3f90c6SAndrew Thompson 	[1] = {
5853a3f90c6SAndrew Thompson 		.type = UE_ISOCHRONOUS,
5863a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
5873a3f90c6SAndrew Thompson 		.direction = UE_DIR_IN,
5884eae601eSAndrew Thompson 		.bufsize = 0,	/* use "wMaxPacketSize * frames" */
589b029f6bbSAndrew Thompson 		.frames = UAUDIO_NFRAMES,
5904eae601eSAndrew Thompson 		.flags = {.short_xfer_ok = 1,},
5914eae601eSAndrew Thompson 		.callback = &uaudio_chan_record_callback,
5923a3f90c6SAndrew Thompson 	},
593b4380da7SHans Petter Selasky 
594b4380da7SHans Petter Selasky 	[2] = {
595b4380da7SHans Petter Selasky 		.type = UE_ISOCHRONOUS,
596b4380da7SHans Petter Selasky 		.endpoint = UE_ADDR_ANY,
597b4380da7SHans Petter Selasky 		.direction = UE_DIR_OUT,
598b4380da7SHans Petter Selasky 		.bufsize = 0,	/* use "wMaxPacketSize * frames" */
599b4380da7SHans Petter Selasky 		.frames = 1,
600b4380da7SHans Petter Selasky 		.flags = {.no_pipe_ok = 1,.short_xfer_ok = 1,},
601b4380da7SHans Petter Selasky 		.callback = &uaudio_chan_record_sync_callback,
602b4380da7SHans Petter Selasky 	},
6033a3f90c6SAndrew Thompson };
6043a3f90c6SAndrew Thompson 
605760bc48eSAndrew Thompson static const struct usb_config
606b4380da7SHans Petter Selasky 	uaudio_cfg_play[UAUDIO_NCHANBUFS + 1] = {
6073a3f90c6SAndrew Thompson 	[0] = {
6083a3f90c6SAndrew Thompson 		.type = UE_ISOCHRONOUS,
6093a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
6103a3f90c6SAndrew Thompson 		.direction = UE_DIR_OUT,
6114eae601eSAndrew Thompson 		.bufsize = 0,	/* use "wMaxPacketSize * frames" */
612b029f6bbSAndrew Thompson 		.frames = UAUDIO_NFRAMES,
6134eae601eSAndrew Thompson 		.flags = {.short_xfer_ok = 1,},
6144eae601eSAndrew Thompson 		.callback = &uaudio_chan_play_callback,
6153a3f90c6SAndrew Thompson 	},
6163a3f90c6SAndrew Thompson 
6173a3f90c6SAndrew Thompson 	[1] = {
6183a3f90c6SAndrew Thompson 		.type = UE_ISOCHRONOUS,
6193a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
6203a3f90c6SAndrew Thompson 		.direction = UE_DIR_OUT,
6214eae601eSAndrew Thompson 		.bufsize = 0,	/* use "wMaxPacketSize * frames" */
622b029f6bbSAndrew Thompson 		.frames = UAUDIO_NFRAMES,
6234eae601eSAndrew Thompson 		.flags = {.short_xfer_ok = 1,},
6244eae601eSAndrew Thompson 		.callback = &uaudio_chan_play_callback,
6253a3f90c6SAndrew Thompson 	},
626b4380da7SHans Petter Selasky 
627b4380da7SHans Petter Selasky 	[2] = {
628b4380da7SHans Petter Selasky 		.type = UE_ISOCHRONOUS,
629b4380da7SHans Petter Selasky 		.endpoint = UE_ADDR_ANY,
630b4380da7SHans Petter Selasky 		.direction = UE_DIR_IN,
631b4380da7SHans Petter Selasky 		.bufsize = 0,	/* use "wMaxPacketSize * frames" */
632b4380da7SHans Petter Selasky 		.frames = 1,
633b4380da7SHans Petter Selasky 		.flags = {.no_pipe_ok = 1,.short_xfer_ok = 1,},
634b4380da7SHans Petter Selasky 		.callback = &uaudio_chan_play_sync_callback,
635b4380da7SHans Petter Selasky 	},
6363a3f90c6SAndrew Thompson };
6373a3f90c6SAndrew Thompson 
638760bc48eSAndrew Thompson static const struct usb_config
6393a3f90c6SAndrew Thompson 	uaudio_mixer_config[1] = {
6403a3f90c6SAndrew Thompson 	[0] = {
6413a3f90c6SAndrew Thompson 		.type = UE_CONTROL,
6423a3f90c6SAndrew Thompson 		.endpoint = 0x00,	/* Control pipe */
6433a3f90c6SAndrew Thompson 		.direction = UE_DIR_ANY,
644760bc48eSAndrew Thompson 		.bufsize = (sizeof(struct usb_device_request) + 4),
6454eae601eSAndrew Thompson 		.callback = &uaudio_mixer_write_cfg_callback,
6464eae601eSAndrew Thompson 		.timeout = 1000,	/* 1 second */
6473a3f90c6SAndrew Thompson 	},
6483a3f90c6SAndrew Thompson };
6493a3f90c6SAndrew Thompson 
6503a3f90c6SAndrew Thompson static const
6513a3f90c6SAndrew Thompson uint8_t	umidi_cmd_to_len[16] = {
6523a3f90c6SAndrew Thompson 	[0x0] = 0,			/* reserved */
6533a3f90c6SAndrew Thompson 	[0x1] = 0,			/* reserved */
6543a3f90c6SAndrew Thompson 	[0x2] = 2,			/* bytes */
6553a3f90c6SAndrew Thompson 	[0x3] = 3,			/* bytes */
6563a3f90c6SAndrew Thompson 	[0x4] = 3,			/* bytes */
6573a3f90c6SAndrew Thompson 	[0x5] = 1,			/* bytes */
6583a3f90c6SAndrew Thompson 	[0x6] = 2,			/* bytes */
6593a3f90c6SAndrew Thompson 	[0x7] = 3,			/* bytes */
6603a3f90c6SAndrew Thompson 	[0x8] = 3,			/* bytes */
6613a3f90c6SAndrew Thompson 	[0x9] = 3,			/* bytes */
6623a3f90c6SAndrew Thompson 	[0xA] = 3,			/* bytes */
6633a3f90c6SAndrew Thompson 	[0xB] = 3,			/* bytes */
6643a3f90c6SAndrew Thompson 	[0xC] = 2,			/* bytes */
6653a3f90c6SAndrew Thompson 	[0xD] = 2,			/* bytes */
6663a3f90c6SAndrew Thompson 	[0xE] = 3,			/* bytes */
6673a3f90c6SAndrew Thompson 	[0xF] = 1,			/* bytes */
6683a3f90c6SAndrew Thompson };
6693a3f90c6SAndrew Thompson 
670760bc48eSAndrew Thompson static const struct usb_config
6713a3f90c6SAndrew Thompson 	umidi_config[UMIDI_N_TRANSFER] = {
6726f068a43SHans Petter Selasky 	[UMIDI_TX_TRANSFER] = {
6733a3f90c6SAndrew Thompson 		.type = UE_BULK,
6743a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
6753a3f90c6SAndrew Thompson 		.direction = UE_DIR_OUT,
676910f1dcfSHans Petter Selasky 		.bufsize = UMIDI_TX_BUFFER,
677d56d9e27SHans Petter Selasky 		.flags = {.no_pipe_ok = 1},
6784eae601eSAndrew Thompson 		.callback = &umidi_bulk_write_callback,
6793a3f90c6SAndrew Thompson 	},
6803a3f90c6SAndrew Thompson 
6816f068a43SHans Petter Selasky 	[UMIDI_RX_TRANSFER] = {
6823a3f90c6SAndrew Thompson 		.type = UE_BULK,
6833a3f90c6SAndrew Thompson 		.endpoint = UE_ADDR_ANY,
6843a3f90c6SAndrew Thompson 		.direction = UE_DIR_IN,
6853851442cSAndrew Thompson 		.bufsize = 4,	/* bytes */
686d56d9e27SHans Petter Selasky 		.flags = {.short_xfer_ok = 1,.proxy_buffer = 1,.no_pipe_ok = 1},
6874eae601eSAndrew Thompson 		.callback = &umidi_bulk_read_callback,
6883a3f90c6SAndrew Thompson 	},
6893a3f90c6SAndrew Thompson };
6903a3f90c6SAndrew Thompson 
69176b71212SHans Petter Selasky static const struct usb_config
69276b71212SHans Petter Selasky 	uaudio_hid_config[UAUDIO_HID_N_TRANSFER] = {
69376b71212SHans Petter Selasky 	[UAUDIO_HID_RX_TRANSFER] = {
69476b71212SHans Petter Selasky 		.type = UE_INTERRUPT,
69576b71212SHans Petter Selasky 		.endpoint = UE_ADDR_ANY,
69676b71212SHans Petter Selasky 		.direction = UE_DIR_IN,
69776b71212SHans Petter Selasky 		.bufsize = 0,	/* use wMaxPacketSize */
69876b71212SHans Petter Selasky 		.flags = {.short_xfer_ok = 1,},
69976b71212SHans Petter Selasky 		.callback = &uaudio_hid_rx_callback,
70076b71212SHans Petter Selasky 	},
70176b71212SHans Petter Selasky };
70276b71212SHans Petter Selasky 
7033a3f90c6SAndrew Thompson static device_method_t uaudio_methods[] = {
7043a3f90c6SAndrew Thompson 	DEVMETHOD(device_probe, uaudio_probe),
7053a3f90c6SAndrew Thompson 	DEVMETHOD(device_attach, uaudio_attach),
7063a3f90c6SAndrew Thompson 	DEVMETHOD(device_detach, uaudio_detach),
7073a3f90c6SAndrew Thompson 	DEVMETHOD(device_suspend, bus_generic_suspend),
7083a3f90c6SAndrew Thompson 	DEVMETHOD(device_resume, bus_generic_resume),
7093a3f90c6SAndrew Thompson 	DEVMETHOD(device_shutdown, bus_generic_shutdown),
7104b7ec270SMarius Strobl 
7114b7ec270SMarius Strobl 	DEVMETHOD_END
7123a3f90c6SAndrew Thompson };
7133a3f90c6SAndrew Thompson 
7143a3f90c6SAndrew Thompson static driver_t uaudio_driver = {
7153a3f90c6SAndrew Thompson 	.name = "uaudio",
7163a3f90c6SAndrew Thompson 	.methods = uaudio_methods,
7173a3f90c6SAndrew Thompson 	.size = sizeof(struct uaudio_softc),
7183a3f90c6SAndrew Thompson };
7193a3f90c6SAndrew Thompson 
72046f0b27aSHans Petter Selasky /* The following table is derived from Linux's quirks-table.h */
72146f0b27aSHans Petter Selasky static const STRUCT_USB_HOST_ID uaudio_vendor_midi[] = {
72246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1000, 0) }, /* UX256 */
72346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1001, 0) }, /* MU1000 */
72446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1002, 0) }, /* MU2000 */
72546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1003, 0) }, /* MU500 */
72646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1004, 3) }, /* UW500 */
72746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1005, 0) }, /* MOTIF6 */
72846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1006, 0) }, /* MOTIF7 */
72946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1007, 0) }, /* MOTIF8 */
73046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1008, 0) }, /* UX96 */
73146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1009, 0) }, /* UX16 */
73246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x100a, 3) }, /* EOS BX */
73346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x100c, 0) }, /* UC-MX */
73446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x100d, 0) }, /* UC-KX */
73546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x100e, 0) }, /* S08 */
73646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x100f, 0) }, /* CLP-150 */
73746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1010, 0) }, /* CLP-170 */
73846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1011, 0) }, /* P-250 */
73946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1012, 0) }, /* TYROS */
74046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1013, 0) }, /* PF-500 */
74146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1014, 0) }, /* S90 */
74246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1015, 0) }, /* MOTIF-R */
74346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1016, 0) }, /* MDP-5 */
74446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1017, 0) }, /* CVP-204 */
74546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1018, 0) }, /* CVP-206 */
74646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1019, 0) }, /* CVP-208 */
74746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x101a, 0) }, /* CVP-210 */
74846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x101b, 0) }, /* PSR-1100 */
74946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x101c, 0) }, /* PSR-2100 */
75046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x101d, 0) }, /* CLP-175 */
75146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x101e, 0) }, /* PSR-K1 */
75246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x101f, 0) }, /* EZ-J24 */
75346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1020, 0) }, /* EZ-250i */
75446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1021, 0) }, /* MOTIF ES 6 */
75546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1022, 0) }, /* MOTIF ES 7 */
75646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1023, 0) }, /* MOTIF ES 8 */
75746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1024, 0) }, /* CVP-301 */
75846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1025, 0) }, /* CVP-303 */
75946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1026, 0) }, /* CVP-305 */
76046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1027, 0) }, /* CVP-307 */
76146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1028, 0) }, /* CVP-309 */
76246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1029, 0) }, /* CVP-309GP */
76346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x102a, 0) }, /* PSR-1500 */
76446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x102b, 0) }, /* PSR-3000 */
76546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x102e, 0) }, /* ELS-01/01C */
76646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1030, 0) }, /* PSR-295/293 */
76746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1031, 0) }, /* DGX-205/203 */
76846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1032, 0) }, /* DGX-305 */
76946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1033, 0) }, /* DGX-505 */
77046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1034, 0) }, /* NULL */
77146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1035, 0) }, /* NULL */
77246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1036, 0) }, /* NULL */
77346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1037, 0) }, /* NULL */
77446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1038, 0) }, /* NULL */
77546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1039, 0) }, /* NULL */
77646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x103a, 0) }, /* NULL */
77746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x103b, 0) }, /* NULL */
77846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x103c, 0) }, /* NULL */
77946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x103d, 0) }, /* NULL */
78046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x103e, 0) }, /* NULL */
78146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x103f, 0) }, /* NULL */
78246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1040, 0) }, /* NULL */
78346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1041, 0) }, /* NULL */
78446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1042, 0) }, /* NULL */
78546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1043, 0) }, /* NULL */
78646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1044, 0) }, /* NULL */
78746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1045, 0) }, /* NULL */
78846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x104e, 0) }, /* NULL */
78946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x104f, 0) }, /* NULL */
79046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1050, 0) }, /* NULL */
79146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1051, 0) }, /* NULL */
79246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1052, 0) }, /* NULL */
79346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1053, 0) }, /* NULL */
79446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1054, 0) }, /* NULL */
79546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1055, 0) }, /* NULL */
79646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1056, 0) }, /* NULL */
79746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1057, 0) }, /* NULL */
79846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1058, 0) }, /* NULL */
79946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1059, 0) }, /* NULL */
80046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x105a, 0) }, /* NULL */
80146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x105b, 0) }, /* NULL */
80246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x105c, 0) }, /* NULL */
80346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x105d, 0) }, /* NULL */
80446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x1503, 3) }, /* MOX6/MOX8 */
80546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x2000, 0) }, /* DGP-7 */
80646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x2001, 0) }, /* DGP-5 */
80746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x2002, 0) }, /* NULL */
80846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x2003, 0) }, /* NULL */
80946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5000, 0) }, /* CS1D */
81046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5001, 0) }, /* DSP1D */
81146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5002, 0) }, /* DME32 */
81246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5003, 0) }, /* DM2000 */
81346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5004, 0) }, /* 02R96 */
81446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5005, 0) }, /* ACU16-C */
81546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5006, 0) }, /* NHB32-C */
81646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5007, 0) }, /* DM1000 */
81746f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5008, 0) }, /* 01V96 */
81846f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x5009, 0) }, /* SPX2000 */
81946f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x500a, 0) }, /* PM5D */
82046f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x500b, 0) }, /* DME64N */
82146f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x500c, 0) }, /* DME24N */
82246f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x500d, 0) }, /* NULL */
82346f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x500e, 0) }, /* NULL */
82446f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x500f, 0) }, /* NULL */
82546f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x7000, 0) }, /* DTX */
82646f0b27aSHans Petter Selasky 	{ USB_VPI(USB_VENDOR_YAMAHA, 0x7010, 0) }, /* UB99 */
82746f0b27aSHans Petter Selasky };
82846f0b27aSHans Petter Selasky 
829f1a16106SHans Petter Selasky static const STRUCT_USB_HOST_ID __used uaudio_devs[] = {
830f1a16106SHans Petter Selasky 	/* Generic USB audio class match */
831f1a16106SHans Petter Selasky 	{USB_IFACE_CLASS(UICLASS_AUDIO),
832f1a16106SHans Petter Selasky 	 USB_IFACE_SUBCLASS(UISUBCLASS_AUDIOCONTROL),},
833f1a16106SHans Petter Selasky 	/* Generic USB MIDI class match */
834f1a16106SHans Petter Selasky 	{USB_IFACE_CLASS(UICLASS_AUDIO),
835f1a16106SHans Petter Selasky 	 USB_IFACE_SUBCLASS(UISUBCLASS_MIDISTREAM),},
836f1a16106SHans Petter Selasky };
837f1a16106SHans Petter Selasky 
83886c9b3f3SHans Petter Selasky static unsigned
uaudio_get_child_index_by_dev(struct uaudio_softc * sc,device_t dev)83986c9b3f3SHans Petter Selasky uaudio_get_child_index_by_dev(struct uaudio_softc *sc, device_t dev)
84086c9b3f3SHans Petter Selasky {
84186c9b3f3SHans Petter Selasky 	unsigned i;
84286c9b3f3SHans Petter Selasky 
84386c9b3f3SHans Petter Selasky 	for (i = 0; i != UAUDIO_MAX_CHILD; i++) {
84486c9b3f3SHans Petter Selasky 		if (dev == sc->sc_child[i].pcm_device)
84586c9b3f3SHans Petter Selasky 			return (i);
84686c9b3f3SHans Petter Selasky 	}
84786c9b3f3SHans Petter Selasky 	panic("uaudio_get_child_index_dev: Invalid device: %p\n", dev);
84886c9b3f3SHans Petter Selasky 	return (0);
84986c9b3f3SHans Petter Selasky }
85086c9b3f3SHans Petter Selasky 
85186c9b3f3SHans Petter Selasky static unsigned
uaudio_get_child_index_by_chan(struct uaudio_softc * sc,struct uaudio_chan * ch)85286c9b3f3SHans Petter Selasky uaudio_get_child_index_by_chan(struct uaudio_softc *sc, struct uaudio_chan *ch)
85386c9b3f3SHans Petter Selasky {
85486c9b3f3SHans Petter Selasky 	unsigned i;
85586c9b3f3SHans Petter Selasky 
85686c9b3f3SHans Petter Selasky 	for (i = 0; i != UAUDIO_MAX_CHILD; i++) {
85786c9b3f3SHans Petter Selasky 		if ((sc->sc_play_chan + i) == ch ||
85886c9b3f3SHans Petter Selasky 		    (sc->sc_rec_chan + i) == ch)
85986c9b3f3SHans Petter Selasky 			return (i);
86086c9b3f3SHans Petter Selasky 	}
86186c9b3f3SHans Petter Selasky 	panic("uaudio_get_child_index_by_chan: Invalid chan: %p\n", ch);
86286c9b3f3SHans Petter Selasky 	return (0);
86386c9b3f3SHans Petter Selasky }
86486c9b3f3SHans Petter Selasky 
8653a3f90c6SAndrew Thompson static int
uaudio_probe(device_t dev)8663a3f90c6SAndrew Thompson uaudio_probe(device_t dev)
8673a3f90c6SAndrew Thompson {
868760bc48eSAndrew Thompson 	struct usb_attach_arg *uaa = device_get_ivars(dev);
8693a3f90c6SAndrew Thompson 
870fadc970bSAndrew Thompson 	if (uaa->usb_mode != USB_MODE_HOST)
8713a3f90c6SAndrew Thompson 		return (ENXIO);
8723a3f90c6SAndrew Thompson 
87346f0b27aSHans Petter Selasky 	/* lookup non-standard device(s) */
87446f0b27aSHans Petter Selasky 
87546f0b27aSHans Petter Selasky 	if (usbd_lookup_id_by_uaa(uaudio_vendor_midi,
87646f0b27aSHans Petter Selasky 	    sizeof(uaudio_vendor_midi), uaa) == 0) {
87746f0b27aSHans Petter Selasky 		return (BUS_PROBE_SPECIFIC);
87846f0b27aSHans Petter Selasky 	}
8793a3f90c6SAndrew Thompson 
88025b74dabSHans Petter Selasky 	if (uaa->info.bInterfaceClass != UICLASS_AUDIO) {
88176eaf537SHans Petter Selasky 		if (uaa->info.bInterfaceClass != UICLASS_VENDOR ||
88276eaf537SHans Petter Selasky 		    usb_test_quirk(uaa, UQ_AU_VENDOR_CLASS) == 0)
88325b74dabSHans Petter Selasky 			return (ENXIO);
88425b74dabSHans Petter Selasky 	}
88525b74dabSHans Petter Selasky 
88625b74dabSHans Petter Selasky 	/* check for AUDIO control interface */
88725b74dabSHans Petter Selasky 
88825b74dabSHans Petter Selasky 	if (uaa->info.bInterfaceSubClass == UISUBCLASS_AUDIOCONTROL) {
889a593f6b8SAndrew Thompson 		if (usb_test_quirk(uaa, UQ_BAD_AUDIO))
8903a3f90c6SAndrew Thompson 			return (ENXIO);
8913a3f90c6SAndrew Thompson 		else
892cd10bffaSAndriy Gapon 			return (BUS_PROBE_GENERIC);
8933a3f90c6SAndrew Thompson 	}
894dc694251SAndrew Thompson 
895dc694251SAndrew Thompson 	/* check for MIDI stream */
896dc694251SAndrew Thompson 
89725b74dabSHans Petter Selasky 	if (uaa->info.bInterfaceSubClass == UISUBCLASS_MIDISTREAM) {
89825b74dabSHans Petter Selasky 		if (usb_test_quirk(uaa, UQ_BAD_MIDI))
89925b74dabSHans Petter Selasky 			return (ENXIO);
90025b74dabSHans Petter Selasky 		else
901cd10bffaSAndriy Gapon 			return (BUS_PROBE_GENERIC);
902dc694251SAndrew Thompson 	}
9033a3f90c6SAndrew Thompson 	return (ENXIO);
9043a3f90c6SAndrew Thompson }
9053a3f90c6SAndrew Thompson 
906067e471aSHans Petter Selasky /*
907067e471aSHans Petter Selasky  * Set Cmedia CM6206 S/PDIF settings
908067e471aSHans Petter Selasky  * Source: CM6206 Datasheet v2.3.
909067e471aSHans Petter Selasky  */
910067e471aSHans Petter Selasky static int
uaudio_set_spdif_cm6206(struct uaudio_softc * sc,int flags)911067e471aSHans Petter Selasky uaudio_set_spdif_cm6206(struct uaudio_softc *sc, int flags)
912067e471aSHans Petter Selasky {
913067e471aSHans Petter Selasky 	uint8_t cmd[2][4] = {
914067e471aSHans Petter Selasky 		{0x20, 0x20, 0x00, 0},
915067e471aSHans Petter Selasky 		{0x20, 0x30, 0x02, 1}
916067e471aSHans Petter Selasky 	};
917067e471aSHans Petter Selasky 	int i;
918067e471aSHans Petter Selasky 
919067e471aSHans Petter Selasky 	if (flags & UAUDIO_SPDIF_OUT)
920067e471aSHans Petter Selasky 		cmd[1][1] = 0x00;
921067e471aSHans Petter Selasky 	else
922067e471aSHans Petter Selasky 		cmd[1][1] = 0x02;
923067e471aSHans Petter Selasky 
924067e471aSHans Petter Selasky 	if (flags & UAUDIO_SPDIF_OUT_96K)
925067e471aSHans Petter Selasky 		cmd[0][1] = 0x60;	/* 96K: 3'b110 */
926067e471aSHans Petter Selasky 
927067e471aSHans Petter Selasky 	if (flags & UAUDIO_SPDIF_IN_MIX)
928067e471aSHans Petter Selasky 		cmd[1][1] = 0x03;	/* SPDIFMIX */
929067e471aSHans Petter Selasky 
930067e471aSHans Petter Selasky 	for (i = 0; i < 2; i++) {
931067e471aSHans Petter Selasky 		if (usbd_req_set_report(sc->sc_udev, NULL,
932067e471aSHans Petter Selasky 		    cmd[i], sizeof(cmd[0]),
933067e471aSHans Petter Selasky 		    sc->sc_mixer_iface_index, UHID_OUTPUT_REPORT, 0) != 0) {
934067e471aSHans Petter Selasky 			return (ENXIO);
935067e471aSHans Petter Selasky 		}
936067e471aSHans Petter Selasky 	}
937067e471aSHans Petter Selasky 	return (0);
938067e471aSHans Petter Selasky }
939067e471aSHans Petter Selasky 
940067e471aSHans Petter Selasky static int
uaudio_set_spdif_dummy(struct uaudio_softc * sc,int flags)941067e471aSHans Petter Selasky uaudio_set_spdif_dummy(struct uaudio_softc *sc, int flags)
942067e471aSHans Petter Selasky {
943067e471aSHans Petter Selasky 	return (0);
944067e471aSHans Petter Selasky }
945067e471aSHans Petter Selasky 
94631070b5bSHans Petter Selasky static usb_error_t
uaudio_force_power_save(struct uaudio_softc * sc,uint8_t iface_index)94731070b5bSHans Petter Selasky uaudio_force_power_save(struct uaudio_softc *sc, uint8_t iface_index)
94831070b5bSHans Petter Selasky {
94931070b5bSHans Petter Selasky 	struct usb_interface *iface;
95031070b5bSHans Petter Selasky 	usb_error_t err;
95131070b5bSHans Petter Selasky 
95231070b5bSHans Petter Selasky 	iface = usbd_get_iface(sc->sc_udev, iface_index);
95331070b5bSHans Petter Selasky 	if (iface == NULL || iface->idesc == NULL)
95431070b5bSHans Petter Selasky 		return (USB_ERR_INVAL);
95531070b5bSHans Petter Selasky 
95631070b5bSHans Petter Selasky 	/* check if correct alternate setting is already selected */
95731070b5bSHans Petter Selasky 	if (iface->alt_index == 0) {
95831070b5bSHans Petter Selasky 		/* force power save mode by selecting default alternate setting */
95931070b5bSHans Petter Selasky 		err = usbd_req_set_alt_interface_no(sc->sc_udev, NULL, iface_index,
96031070b5bSHans Petter Selasky 		    iface->idesc->bAlternateSetting);
96131070b5bSHans Petter Selasky 	} else {
96231070b5bSHans Petter Selasky 		err = usbd_set_alt_interface_index(sc->sc_udev, iface_index, 0);
96331070b5bSHans Petter Selasky 	}
96431070b5bSHans Petter Selasky 	return (err);
96531070b5bSHans Petter Selasky }
96631070b5bSHans Petter Selasky 
9673a3f90c6SAndrew Thompson static int
uaudio_attach(device_t dev)9683a3f90c6SAndrew Thompson uaudio_attach(device_t dev)
9693a3f90c6SAndrew Thompson {
970760bc48eSAndrew Thompson 	struct usb_attach_arg *uaa = device_get_ivars(dev);
9713a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(dev);
972760bc48eSAndrew Thompson 	struct usb_interface_descriptor *id;
97333da3daaSHans Petter Selasky 	usb_error_t err;
97486c9b3f3SHans Petter Selasky 	unsigned i;
9753a3f90c6SAndrew Thompson 
9763a3f90c6SAndrew Thompson 	sc->sc_udev = uaa->device;
977b029f6bbSAndrew Thompson 	sc->sc_mixer_iface_index = uaa->info.bIfaceIndex;
978b029f6bbSAndrew Thompson 	sc->sc_mixer_iface_no = uaa->info.bIfaceNum;
979455a367fSHans Petter Selasky 	sc->sc_config_msg[0].hdr.pm_callback = &uaudio_configure_msg;
980455a367fSHans Petter Selasky 	sc->sc_config_msg[0].sc = sc;
981455a367fSHans Petter Selasky 	sc->sc_config_msg[1].hdr.pm_callback = &uaudio_configure_msg;
982455a367fSHans Petter Selasky 	sc->sc_config_msg[1].sc = sc;
9833a3f90c6SAndrew Thompson 
984a593f6b8SAndrew Thompson 	if (usb_test_quirk(uaa, UQ_AUDIO_SWAP_LR))
9853a3f90c6SAndrew Thompson 		sc->sc_uq_audio_swap_lr = 1;
9863a3f90c6SAndrew Thompson 
987a593f6b8SAndrew Thompson 	if (usb_test_quirk(uaa, UQ_AU_INP_ASYNC))
9883a3f90c6SAndrew Thompson 		sc->sc_uq_au_inp_async = 1;
9893a3f90c6SAndrew Thompson 
990a593f6b8SAndrew Thompson 	if (usb_test_quirk(uaa, UQ_AU_NO_XU))
9913a3f90c6SAndrew Thompson 		sc->sc_uq_au_no_xu = 1;
9923a3f90c6SAndrew Thompson 
993a593f6b8SAndrew Thompson 	if (usb_test_quirk(uaa, UQ_BAD_ADC))
9943a3f90c6SAndrew Thompson 		sc->sc_uq_bad_adc = 1;
9953a3f90c6SAndrew Thompson 
99625b74dabSHans Petter Selasky 	if (usb_test_quirk(uaa, UQ_AU_VENDOR_CLASS))
99725b74dabSHans Petter Selasky 		sc->sc_uq_au_vendor_class = 1;
99825b74dabSHans Petter Selasky 
999067e471aSHans Petter Selasky 	/* set S/PDIF function */
1000067e471aSHans Petter Selasky 	if (usb_test_quirk(uaa, UQ_AU_SET_SPDIF_CM6206))
1001067e471aSHans Petter Selasky 		sc->sc_set_spdif_fn = uaudio_set_spdif_cm6206;
1002067e471aSHans Petter Selasky 	else
1003067e471aSHans Petter Selasky 		sc->sc_set_spdif_fn = uaudio_set_spdif_dummy;
1004067e471aSHans Petter Selasky 
10053a3f90c6SAndrew Thompson 	umidi_init(dev);
10063a3f90c6SAndrew Thompson 
1007a593f6b8SAndrew Thompson 	device_set_usb_desc(dev);
10083a3f90c6SAndrew Thompson 
1009a593f6b8SAndrew Thompson 	id = usbd_get_interface_descriptor(uaa->iface);
10103a3f90c6SAndrew Thompson 
1011e2524b2eSHans Petter Selasky 	/* must fill mixer info before channel info */
10123a3f90c6SAndrew Thompson 	uaudio_mixer_fill_info(sc, uaa->device, id);
10133a3f90c6SAndrew Thompson 
1014e2524b2eSHans Petter Selasky 	/* fill channel info */
1015e2524b2eSHans Petter Selasky 	uaudio_chan_fill_info(sc, uaa->device);
1016e2524b2eSHans Petter Selasky 
10173a3f90c6SAndrew Thompson 	DPRINTF("audio rev %d.%02x\n",
10183a3f90c6SAndrew Thompson 	    sc->sc_audio_rev >> 8,
10193a3f90c6SAndrew Thompson 	    sc->sc_audio_rev & 0xff);
10203a3f90c6SAndrew Thompson 
1021ff4d5953SHans Petter Selasky 	if (sc->sc_mixer_count == 0) {
1022ff4d5953SHans Petter Selasky 		if (uaa->info.idVendor == USB_VENDOR_MAUDIO &&
1023ff4d5953SHans Petter Selasky 		    (uaa->info.idProduct == USB_PRODUCT_MAUDIO_FASTTRACKULTRA ||
1024ff4d5953SHans Petter Selasky 		    uaa->info.idProduct == USB_PRODUCT_MAUDIO_FASTTRACKULTRA8R)) {
1025ff4d5953SHans Petter Selasky 			DPRINTF("Generating mixer descriptors\n");
1026ff4d5953SHans Petter Selasky 			uaudio_mixer_controls_create_ftu(sc);
1027ff4d5953SHans Petter Selasky 		}
1028ff4d5953SHans Petter Selasky 	}
1029ff4d5953SHans Petter Selasky 
10303a3f90c6SAndrew Thompson 	DPRINTF("%d mixer controls\n",
10313a3f90c6SAndrew Thompson 	    sc->sc_mixer_count);
10323a3f90c6SAndrew Thompson 
103386c9b3f3SHans Petter Selasky 	for (i = 0; i != UAUDIO_MAX_CHILD; i++) {
1034455a367fSHans Petter Selasky 		uint8_t x;
103533da3daaSHans Petter Selasky 
103686c9b3f3SHans Petter Selasky 		if (sc->sc_play_chan[i].num_alt <= 0)
103786c9b3f3SHans Petter Selasky 			break;
103886c9b3f3SHans Petter Selasky 
103933da3daaSHans Petter Selasky 		/*
104033da3daaSHans Petter Selasky 		 * Need to set a default alternate interface, else
104131070b5bSHans Petter Selasky 		 * some USB audio devices might go into an infinite
104233da3daaSHans Petter Selasky 		 * re-enumeration loop:
104333da3daaSHans Petter Selasky 		 */
104431070b5bSHans Petter Selasky 		err = uaudio_force_power_save(sc,
104531070b5bSHans Petter Selasky 		    sc->sc_play_chan[i].usb_alt[0].iface_index);
104633da3daaSHans Petter Selasky 		if (err) {
104733da3daaSHans Petter Selasky 			DPRINTF("setting of alternate index failed: %s!\n",
104833da3daaSHans Petter Selasky 			    usbd_errstr(err));
104933da3daaSHans Petter Selasky 		}
105031070b5bSHans Petter Selasky 
105186c9b3f3SHans Petter Selasky 		for (x = 0; x != sc->sc_play_chan[i].num_alt; x++) {
105286c9b3f3SHans Petter Selasky 			device_printf(dev, "Play[%u]: %d Hz, %d ch, %s format, "
1053f60e99f4SChristos Margiolis 			    "2x%dms buffer.%s\n", i,
105486c9b3f3SHans Petter Selasky 			    sc->sc_play_chan[i].usb_alt[x].sample_rate,
105586c9b3f3SHans Petter Selasky 			    sc->sc_play_chan[i].usb_alt[x].channels,
105642a18f32SHans Petter Selasky 			    sc->sc_play_chan[i].usb_alt[x].p_fmt->description,
1057f60e99f4SChristos Margiolis 			    uaudio_buffer_ms,
1058f60e99f4SChristos Margiolis 			    (x == 0) ? " (selected)" : "");
1059455a367fSHans Petter Selasky 		}
106086c9b3f3SHans Petter Selasky 	}
106186c9b3f3SHans Petter Selasky 	if (i == 0)
10629b5da816SHans Petter Selasky 		device_printf(dev, "No playback.\n");
10633a3f90c6SAndrew Thompson 
106486c9b3f3SHans Petter Selasky 	for (i = 0; i != UAUDIO_MAX_CHILD; i++) {
1065455a367fSHans Petter Selasky 		uint8_t x;
106633da3daaSHans Petter Selasky 
106786c9b3f3SHans Petter Selasky 		if (sc->sc_rec_chan[i].num_alt <= 0)
106886c9b3f3SHans Petter Selasky 			break;
106986c9b3f3SHans Petter Selasky 
107033da3daaSHans Petter Selasky 		/*
107133da3daaSHans Petter Selasky 		 * Need to set a default alternate interface, else
107231070b5bSHans Petter Selasky 		 * some USB audio devices might go into an infinite
107333da3daaSHans Petter Selasky 		 * re-enumeration loop:
107433da3daaSHans Petter Selasky 		 */
107531070b5bSHans Petter Selasky 		err = uaudio_force_power_save(sc,
107631070b5bSHans Petter Selasky 		    sc->sc_rec_chan[i].usb_alt[0].iface_index);
107733da3daaSHans Petter Selasky 		if (err) {
107833da3daaSHans Petter Selasky 			DPRINTF("setting of alternate index failed: %s!\n",
107933da3daaSHans Petter Selasky 			    usbd_errstr(err));
108033da3daaSHans Petter Selasky 		}
108131070b5bSHans Petter Selasky 
108286c9b3f3SHans Petter Selasky 		for (x = 0; x != sc->sc_rec_chan[i].num_alt; x++) {
108386c9b3f3SHans Petter Selasky 			device_printf(dev, "Record[%u]: %d Hz, %d ch, %s format, "
1084f60e99f4SChristos Margiolis 			    "2x%dms buffer.%s\n", i,
108586c9b3f3SHans Petter Selasky 			    sc->sc_rec_chan[i].usb_alt[x].sample_rate,
108686c9b3f3SHans Petter Selasky 			    sc->sc_rec_chan[i].usb_alt[x].channels,
108742a18f32SHans Petter Selasky 			    sc->sc_rec_chan[i].usb_alt[x].p_fmt->description,
1088f60e99f4SChristos Margiolis 			    uaudio_buffer_ms,
1089f60e99f4SChristos Margiolis 			    (x == 0) ? " (selected)" : "");
1090455a367fSHans Petter Selasky 		}
109186c9b3f3SHans Petter Selasky 	}
109286c9b3f3SHans Petter Selasky 	if (i == 0)
10939b5da816SHans Petter Selasky 		device_printf(dev, "No recording.\n");
10943a3f90c6SAndrew Thompson 
109546f0b27aSHans Petter Selasky 	if (sc->sc_midi_chan.valid == 0) {
109646f0b27aSHans Petter Selasky 		if (usbd_lookup_id_by_uaa(uaudio_vendor_midi,
109746f0b27aSHans Petter Selasky 		    sizeof(uaudio_vendor_midi), uaa) == 0) {
109846f0b27aSHans Petter Selasky 			sc->sc_midi_chan.iface_index =
109946f0b27aSHans Petter Selasky 			    (uint8_t)uaa->driver_info;
110046f0b27aSHans Petter Selasky 			sc->sc_midi_chan.iface_alt_index = 0;
110146f0b27aSHans Petter Selasky 			sc->sc_midi_chan.valid = 1;
110246f0b27aSHans Petter Selasky 		}
110346f0b27aSHans Petter Selasky 	}
110446f0b27aSHans Petter Selasky 
11053a3f90c6SAndrew Thompson 	if (sc->sc_midi_chan.valid) {
11063a3f90c6SAndrew Thompson 		if (umidi_probe(dev)) {
11073a3f90c6SAndrew Thompson 			goto detach;
11083a3f90c6SAndrew Thompson 		}
11099b5da816SHans Petter Selasky 		device_printf(dev, "MIDI sequencer.\n");
11103a3f90c6SAndrew Thompson 	} else {
111176b71212SHans Petter Selasky 		device_printf(dev, "No MIDI sequencer.\n");
11123a3f90c6SAndrew Thompson 	}
11133a3f90c6SAndrew Thompson 
11143a3f90c6SAndrew Thompson 	DPRINTF("doing child attach\n");
11153a3f90c6SAndrew Thompson 
11163a3f90c6SAndrew Thompson 	/* attach the children */
11173a3f90c6SAndrew Thompson 
11183a3f90c6SAndrew Thompson 	sc->sc_sndcard_func.func = SCF_PCM;
11193a3f90c6SAndrew Thompson 
11209b5da816SHans Petter Selasky 	/*
11219b5da816SHans Petter Selasky 	 * Only attach a PCM device if we have a playback, recording
11229b5da816SHans Petter Selasky 	 * or mixer device present:
11239b5da816SHans Petter Selasky 	 */
112486c9b3f3SHans Petter Selasky 	for (i = 0; i != UAUDIO_MAX_CHILD; i++) {
112586c9b3f3SHans Petter Selasky 		if (sc->sc_play_chan[i].num_alt <= 0 &&
112686c9b3f3SHans Petter Selasky 		    sc->sc_rec_chan[i].num_alt <= 0 &&
112786c9b3f3SHans Petter Selasky 		    sc->sc_child[i].mix_info == 0)
112886c9b3f3SHans Petter Selasky 			continue;
112986c9b3f3SHans Petter Selasky 		sc->sc_child[i].pcm_device =
113086c9b3f3SHans Petter Selasky 		    device_add_child(dev, "pcm", -1);
11313a3f90c6SAndrew Thompson 
113286c9b3f3SHans Petter Selasky 		if (sc->sc_child[i].pcm_device == NULL) {
11333a3f90c6SAndrew Thompson 			DPRINTF("out of memory\n");
11343a3f90c6SAndrew Thompson 			goto detach;
11353a3f90c6SAndrew Thompson 		}
113686c9b3f3SHans Petter Selasky 		device_set_ivars(sc->sc_child[i].pcm_device,
113786c9b3f3SHans Petter Selasky 		    &sc->sc_sndcard_func);
11389b5da816SHans Petter Selasky 	}
11393a3f90c6SAndrew Thompson 
11403a3f90c6SAndrew Thompson 	if (bus_generic_attach(dev)) {
11413a3f90c6SAndrew Thompson 		DPRINTF("child attach failed\n");
11423a3f90c6SAndrew Thompson 		goto detach;
11433a3f90c6SAndrew Thompson 	}
1144902514f6SHans Petter Selasky 
1145f14436adSHans Petter Selasky 	if (uaudio_handle_hid) {
114676b71212SHans Petter Selasky 		if (uaudio_hid_probe(sc, uaa) == 0) {
114776b71212SHans Petter Selasky 			device_printf(dev, "HID volume keys found.\n");
114876b71212SHans Petter Selasky 		} else {
114976b71212SHans Petter Selasky 			device_printf(dev, "No HID volume keys found.\n");
115076b71212SHans Petter Selasky 		}
1151f14436adSHans Petter Selasky 	}
115276b71212SHans Petter Selasky 
1153902514f6SHans Petter Selasky 	/* reload all mixer settings */
1154902514f6SHans Petter Selasky 	uaudio_mixer_reload_all(sc);
1155902514f6SHans Petter Selasky 
1156067e471aSHans Petter Selasky 	/* enable S/PDIF output, if any */
1157067e471aSHans Petter Selasky 	if (sc->sc_set_spdif_fn(sc,
1158067e471aSHans Petter Selasky 	    UAUDIO_SPDIF_OUT | UAUDIO_SPDIF_OUT_48K) != 0) {
1159067e471aSHans Petter Selasky 		device_printf(dev, "Failed to enable S/PDIF at 48K\n");
1160067e471aSHans Petter Selasky 	}
11613a3f90c6SAndrew Thompson 	return (0);			/* success */
11623a3f90c6SAndrew Thompson 
11633a3f90c6SAndrew Thompson detach:
11643a3f90c6SAndrew Thompson 	uaudio_detach(dev);
11653a3f90c6SAndrew Thompson 	return (ENXIO);
11663a3f90c6SAndrew Thompson }
11673a3f90c6SAndrew Thompson 
11683a3f90c6SAndrew Thompson static void
uaudio_pcm_setflags(device_t dev,uint32_t flags)11693a3f90c6SAndrew Thompson uaudio_pcm_setflags(device_t dev, uint32_t flags)
11703a3f90c6SAndrew Thompson {
11713a3f90c6SAndrew Thompson 	pcm_setflags(dev, pcm_getflags(dev) | flags);
11723a3f90c6SAndrew Thompson }
11733a3f90c6SAndrew Thompson 
11743a3f90c6SAndrew Thompson int
uaudio_attach_sub(device_t dev,kobj_class_t mixer_class,kobj_class_t chan_class)11753a3f90c6SAndrew Thompson uaudio_attach_sub(device_t dev, kobj_class_t mixer_class, kobj_class_t chan_class)
11763a3f90c6SAndrew Thompson {
11773a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(device_get_parent(dev));
117886c9b3f3SHans Petter Selasky 	unsigned i = uaudio_get_child_index_by_dev(sc, dev);
1179f7d3d0a4SChristos Margiolis 	char status[SND_STATUSLEN];
11803a3f90c6SAndrew Thompson 
118186c9b3f3SHans Petter Selasky 	uaudio_mixer_init(sc, i);
11823a3f90c6SAndrew Thompson 
11833a3f90c6SAndrew Thompson 	if (sc->sc_uq_audio_swap_lr) {
11843a3f90c6SAndrew Thompson 		DPRINTF("hardware has swapped left and right\n");
118590da2b28SAriff Abdullah 		/* uaudio_pcm_setflags(dev, SD_F_PSWAPLR); */
11863a3f90c6SAndrew Thompson 	}
118786c9b3f3SHans Petter Selasky 	if (sc->sc_play_chan[i].num_alt > 0 &&
118886c9b3f3SHans Petter Selasky 	    (sc->sc_child[i].mix_info & SOUND_MASK_PCM) == 0) {
118975dc9c41SHans Petter Selasky 		DPRINTF("software controlled main volume\n");
11903a3f90c6SAndrew Thompson 
11913a3f90c6SAndrew Thompson 		/*
11923a3f90c6SAndrew Thompson 		 * Emulate missing pcm mixer controller
11933a3f90c6SAndrew Thompson 		 * through FEEDER_VOLUME
11943a3f90c6SAndrew Thompson 		 */
11953a3f90c6SAndrew Thompson 		uaudio_pcm_setflags(dev, SD_F_SOFTPCMVOL);
11963a3f90c6SAndrew Thompson 	}
11979dd12733SHans Petter Selasky 	if (sc->sc_pcm_bitperfect) {
11989dd12733SHans Petter Selasky 		DPRINTF("device needs bitperfect by default\n");
11999dd12733SHans Petter Selasky 		uaudio_pcm_setflags(dev, SD_F_BITPERFECT);
12009dd12733SHans Petter Selasky 	}
1201902514f6SHans Petter Selasky 	if (mixer_init(dev, mixer_class, sc))
12023a3f90c6SAndrew Thompson 		goto detach;
120386c9b3f3SHans Petter Selasky 	sc->sc_child[i].mixer_init = 1;
12043a3f90c6SAndrew Thompson 
12052ba0f361SHans Petter Selasky 	mixer_hwvol_init(dev);
12062ba0f361SHans Petter Selasky 
1207f7d3d0a4SChristos Margiolis 	device_set_descf(dev, "%s %s",
120818d87fe4SChristos Margiolis 	    usb_get_manufacturer(sc->sc_udev),
120918d87fe4SChristos Margiolis 	    usb_get_product(sc->sc_udev));
121018d87fe4SChristos Margiolis 
1211837cd192SChristos Margiolis 	snprintf(status, sizeof(status), "on %s",
1212837cd192SChristos Margiolis 	    device_get_nameunit(device_get_parent(dev)));
12133a3f90c6SAndrew Thompson 
12143a3f90c6SAndrew Thompson 	if (pcm_register(dev, sc,
121586c9b3f3SHans Petter Selasky 	    (sc->sc_play_chan[i].num_alt > 0) ? 1 : 0,
121686c9b3f3SHans Petter Selasky 	    (sc->sc_rec_chan[i].num_alt > 0) ? 1 : 0)) {
12173a3f90c6SAndrew Thompson 		goto detach;
12183a3f90c6SAndrew Thompson 	}
121990da2b28SAriff Abdullah 
122090da2b28SAriff Abdullah 	uaudio_pcm_setflags(dev, SD_F_MPSAFE);
122186c9b3f3SHans Petter Selasky 	sc->sc_child[i].pcm_registered = 1;
12223a3f90c6SAndrew Thompson 
122386c9b3f3SHans Petter Selasky 	if (sc->sc_play_chan[i].num_alt > 0) {
122486c9b3f3SHans Petter Selasky 		sc->sc_play_chan[i].priv_sc = sc;
122586c9b3f3SHans Petter Selasky 		pcm_addchan(dev, PCMDIR_PLAY, chan_class,
122686c9b3f3SHans Petter Selasky 		    &sc->sc_play_chan[i]);
12273a3f90c6SAndrew Thompson 	}
122886c9b3f3SHans Petter Selasky 
122986c9b3f3SHans Petter Selasky 	if (sc->sc_rec_chan[i].num_alt > 0) {
123086c9b3f3SHans Petter Selasky 		sc->sc_rec_chan[i].priv_sc = sc;
123186c9b3f3SHans Petter Selasky 		pcm_addchan(dev, PCMDIR_REC, chan_class,
123286c9b3f3SHans Petter Selasky 		    &sc->sc_rec_chan[i]);
12333a3f90c6SAndrew Thompson 	}
12343a3f90c6SAndrew Thompson 	pcm_setstatus(dev, status);
12353a3f90c6SAndrew Thompson 
123686c9b3f3SHans Petter Selasky 	uaudio_mixer_register_sysctl(sc, dev, i);
1237902514f6SHans Petter Selasky 
123885bad582SHans Petter Selasky 	SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
123985bad582SHans Petter Selasky 	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
124086c9b3f3SHans Petter Selasky 	    "feedback_rate", CTLFLAG_RD, &sc->sc_play_chan[i].feedback_rate,
124185bad582SHans Petter Selasky 	    0, "Feedback sample rate in Hz");
124285bad582SHans Petter Selasky 
12433a3f90c6SAndrew Thompson 	return (0);			/* success */
12443a3f90c6SAndrew Thompson 
12453a3f90c6SAndrew Thompson detach:
12463a3f90c6SAndrew Thompson 	uaudio_detach_sub(dev);
12473a3f90c6SAndrew Thompson 	return (ENXIO);
12483a3f90c6SAndrew Thompson }
12493a3f90c6SAndrew Thompson 
12503a3f90c6SAndrew Thompson int
uaudio_detach_sub(device_t dev)12513a3f90c6SAndrew Thompson uaudio_detach_sub(device_t dev)
12523a3f90c6SAndrew Thompson {
12533a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(device_get_parent(dev));
125486c9b3f3SHans Petter Selasky 	unsigned i = uaudio_get_child_index_by_dev(sc, dev);
12553a3f90c6SAndrew Thompson 	int error = 0;
12563a3f90c6SAndrew Thompson 
125786c9b3f3SHans Petter Selasky 	if (sc->sc_child[i].pcm_registered) {
12583a3f90c6SAndrew Thompson 		error = pcm_unregister(dev);
125944e128feSChristos Margiolis 	} else if (sc->sc_child[i].mixer_init) {
12603a3f90c6SAndrew Thompson 		error = mixer_uninit(dev);
12613a3f90c6SAndrew Thompson 	}
12623a3f90c6SAndrew Thompson 
126344e128feSChristos Margiolis 	return (error);
12643a3f90c6SAndrew Thompson }
12653a3f90c6SAndrew Thompson 
12663a3f90c6SAndrew Thompson static int
uaudio_detach(device_t dev)12673a3f90c6SAndrew Thompson uaudio_detach(device_t dev)
12683a3f90c6SAndrew Thompson {
12693a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(dev);
127086c9b3f3SHans Petter Selasky 	unsigned i;
12713a3f90c6SAndrew Thompson 
1272a6ed4937SHans Petter Selasky 	/*
1273a6ed4937SHans Petter Selasky 	 * Stop USB transfers early so that any audio applications
1274a6ed4937SHans Petter Selasky 	 * will time out and close opened /dev/dspX.Y device(s), if
1275a6ed4937SHans Petter Selasky 	 * any.
1276a6ed4937SHans Petter Selasky 	 */
1277455a367fSHans Petter Selasky 	usb_proc_explore_lock(sc->sc_udev);
127886c9b3f3SHans Petter Selasky 	for (i = 0; i != UAUDIO_MAX_CHILD; i++) {
127986c9b3f3SHans Petter Selasky 		sc->sc_play_chan[i].operation = CHAN_OP_DRAIN;
128086c9b3f3SHans Petter Selasky 		sc->sc_rec_chan[i].operation = CHAN_OP_DRAIN;
128186c9b3f3SHans Petter Selasky 	}
1282455a367fSHans Petter Selasky 	usb_proc_explore_mwait(sc->sc_udev,
1283455a367fSHans Petter Selasky 	    &sc->sc_config_msg[0], &sc->sc_config_msg[1]);
1284455a367fSHans Petter Selasky 	usb_proc_explore_unlock(sc->sc_udev);
1285455a367fSHans Petter Selasky 
128686c9b3f3SHans Petter Selasky 	for (i = 0; i != UAUDIO_MAX_CHILD; i++) {
128786c9b3f3SHans Petter Selasky 		usbd_transfer_unsetup(sc->sc_play_chan[i].xfer, UAUDIO_NCHANBUFS + 1);
128886c9b3f3SHans Petter Selasky 		usbd_transfer_unsetup(sc->sc_rec_chan[i].xfer, UAUDIO_NCHANBUFS + 1);
128986c9b3f3SHans Petter Selasky 	}
1290a6ed4937SHans Petter Selasky 
129176b71212SHans Petter Selasky 	uaudio_hid_detach(sc);
129276b71212SHans Petter Selasky 
1293a6ed4937SHans Petter Selasky 	if (bus_generic_detach(dev) != 0) {
12943a3f90c6SAndrew Thompson 		DPRINTF("detach failed!\n");
12953a3f90c6SAndrew Thompson 	}
12963a3f90c6SAndrew Thompson 	sbuf_delete(&sc->sc_sndstat);
12973a3f90c6SAndrew Thompson 	sc->sc_sndstat_valid = 0;
12983a3f90c6SAndrew Thompson 
12993a3f90c6SAndrew Thompson 	umidi_detach(dev);
13003a3f90c6SAndrew Thompson 
1301902514f6SHans Petter Selasky 	/* free mixer data */
1302902514f6SHans Petter Selasky 
1303902514f6SHans Petter Selasky 	uaudio_mixer_ctl_free(sc);
1304902514f6SHans Petter Selasky 
130586c9b3f3SHans Petter Selasky 	/* disable S/PDIF output, if any */
130686c9b3f3SHans Petter Selasky 	(void) sc->sc_set_spdif_fn(sc, 0);
130786c9b3f3SHans Petter Selasky 
13083a3f90c6SAndrew Thompson 	return (0);
13093a3f90c6SAndrew Thompson }
13103a3f90c6SAndrew Thompson 
1311455a367fSHans Petter Selasky static uint32_t
uaudio_get_interval_frames(const usb_endpoint_descriptor_audio_t * ed)1312b2e97edfSFlorian Walpen uaudio_get_interval_frames(const usb_endpoint_descriptor_audio_t *ed)
1313b2e97edfSFlorian Walpen {
1314b2e97edfSFlorian Walpen 	uint32_t frames = 1;
1315b2e97edfSFlorian Walpen 	/* Isochronous transfer interval is 2^(bInterval - 1) frames. */
1316b2e97edfSFlorian Walpen 	if (ed->bInterval >= 1 && ed->bInterval <= 16)
1317b2e97edfSFlorian Walpen 		frames = (1 << (ed->bInterval - 1));
1318b2e97edfSFlorian Walpen 	/* Limit transfer interval to maximum number of frames. */
1319b2e97edfSFlorian Walpen 	if (frames > UAUDIO_NFRAMES)
1320b2e97edfSFlorian Walpen 		frames = UAUDIO_NFRAMES;
1321b2e97edfSFlorian Walpen 	return (frames);
1322b2e97edfSFlorian Walpen }
1323b2e97edfSFlorian Walpen 
1324b2e97edfSFlorian Walpen static uint32_t
uaudio_get_buffer_ms(struct uaudio_softc * sc,uint32_t int_frames)1325b2e97edfSFlorian Walpen uaudio_get_buffer_ms(struct uaudio_softc *sc, uint32_t int_frames)
1326b2e97edfSFlorian Walpen {
1327b2e97edfSFlorian Walpen 	uint32_t ms = 1;
1328b2e97edfSFlorian Walpen 	uint32_t fps = usbd_get_isoc_fps(sc->sc_udev);
1329b2e97edfSFlorian Walpen 	/* Make sure a whole USB transfer interval fits into the buffer. */
1330b2e97edfSFlorian Walpen 	if (fps >= 1000 && int_frames > 0 && int_frames <= UAUDIO_NFRAMES) {
1331b2e97edfSFlorian Walpen 		/* Convert interval frames to milliseconds. */
1332b2e97edfSFlorian Walpen 		ms = ((int_frames * 1000) / fps);
1333b2e97edfSFlorian Walpen 	}
1334b2e97edfSFlorian Walpen 	/* Respect minimum buffer length set through buffer_ms tunable. */
1335b2e97edfSFlorian Walpen 	if (ms < uaudio_buffer_ms)
1336b2e97edfSFlorian Walpen 		ms = uaudio_buffer_ms;
1337b2e97edfSFlorian Walpen 	/* Limit buffer length to 8 milliseconds. */
1338b2e97edfSFlorian Walpen 	if (ms > UAUDIO_BUFFER_MS_MAX)
1339b2e97edfSFlorian Walpen 		ms = UAUDIO_BUFFER_MS_MAX;
1340b2e97edfSFlorian Walpen 	return (ms);
1341b2e97edfSFlorian Walpen }
1342b2e97edfSFlorian Walpen 
1343b2e97edfSFlorian Walpen static uint32_t
uaudio_get_buffer_size(struct uaudio_chan * ch,uint8_t alt)1344455a367fSHans Petter Selasky uaudio_get_buffer_size(struct uaudio_chan *ch, uint8_t alt)
1345455a367fSHans Petter Selasky {
1346455a367fSHans Petter Selasky 	struct uaudio_chan_alt *chan_alt = &ch->usb_alt[alt];
1347b2e97edfSFlorian Walpen 	uint32_t int_frames, ms, buf_size;
1348b2e97edfSFlorian Walpen 	/* USB transfer interval in frames, from endpoint descriptor. */
1349b2e97edfSFlorian Walpen 	int_frames = uaudio_get_interval_frames(chan_alt->p_ed1);
1350b2e97edfSFlorian Walpen 	/* Buffer length in milliseconds, and in bytes of audio data. */
1351b2e97edfSFlorian Walpen 	ms = uaudio_get_buffer_ms(ch->priv_sc, int_frames);
1352b2e97edfSFlorian Walpen 	buf_size = chan_alt->sample_size *
1353b2e97edfSFlorian Walpen 	    howmany(chan_alt->sample_rate * ms, 1000);
1354b2e97edfSFlorian Walpen 	return (buf_size);
1355b2e97edfSFlorian Walpen }
1356b2e97edfSFlorian Walpen 
1357b2e97edfSFlorian Walpen static uint32_t
uaudio_max_buffer_size(struct uaudio_chan * ch,uint8_t alt)1358b2e97edfSFlorian Walpen uaudio_max_buffer_size(struct uaudio_chan *ch, uint8_t alt)
1359b2e97edfSFlorian Walpen {
1360b2e97edfSFlorian Walpen 	struct uaudio_chan_alt *chan_alt = &ch->usb_alt[alt];
1361b2e97edfSFlorian Walpen 	uint32_t buf_size;
1362b2e97edfSFlorian Walpen 	/* Maximum buffer length is 8 milliseconds. */
1363b2e97edfSFlorian Walpen 	buf_size = chan_alt->sample_size *
1364b2e97edfSFlorian Walpen 	    howmany(chan_alt->sample_rate * UAUDIO_BUFFER_MS_MAX, 1000);
1365455a367fSHans Petter Selasky 	return (buf_size);
1366455a367fSHans Petter Selasky }
1367455a367fSHans Petter Selasky 
1368455a367fSHans Petter Selasky static void
uaudio_configure_msg_sub(struct uaudio_softc * sc,struct uaudio_chan * chan,int dir)1369455a367fSHans Petter Selasky uaudio_configure_msg_sub(struct uaudio_softc *sc,
1370455a367fSHans Petter Selasky     struct uaudio_chan *chan, int dir)
1371455a367fSHans Petter Selasky {
1372455a367fSHans Petter Selasky 	struct uaudio_chan_alt *chan_alt;
1373455a367fSHans Petter Selasky 	uint32_t frames;
1374455a367fSHans Petter Selasky 	uint32_t buf_size;
1375455a367fSHans Petter Selasky 	uint16_t fps;
137631070b5bSHans Petter Selasky 	uint8_t next_alt;
1377455a367fSHans Petter Selasky 	uint8_t fps_shift;
1378455a367fSHans Petter Selasky 	uint8_t operation;
1379455a367fSHans Petter Selasky 	usb_error_t err;
1380455a367fSHans Petter Selasky 
1381455a367fSHans Petter Selasky 	if (chan->num_alt <= 0)
1382455a367fSHans Petter Selasky 		return;
1383455a367fSHans Petter Selasky 
1384455a367fSHans Petter Selasky 	DPRINTF("\n");
1385455a367fSHans Petter Selasky 
1386455a367fSHans Petter Selasky 	usb_proc_explore_lock(sc->sc_udev);
1387455a367fSHans Petter Selasky 	operation = chan->operation;
138831070b5bSHans Petter Selasky 	switch (operation) {
138931070b5bSHans Petter Selasky 	case CHAN_OP_START:
139031070b5bSHans Petter Selasky 	case CHAN_OP_STOP:
1391455a367fSHans Petter Selasky 		chan->operation = CHAN_OP_NONE;
139231070b5bSHans Petter Selasky 		break;
139331070b5bSHans Petter Selasky 	default:
139431070b5bSHans Petter Selasky 		break;
139531070b5bSHans Petter Selasky 	}
1396455a367fSHans Petter Selasky 	usb_proc_explore_unlock(sc->sc_udev);
1397455a367fSHans Petter Selasky 
139831070b5bSHans Petter Selasky 	switch (operation) {
139931070b5bSHans Petter Selasky 	case CHAN_OP_STOP:
140031070b5bSHans Petter Selasky 		/* Unsetup prior USB transfers, if any. */
140131070b5bSHans Petter Selasky 		usbd_transfer_unsetup(chan->xfer, UAUDIO_NCHANBUFS + 1);
140231070b5bSHans Petter Selasky 
1403455a367fSHans Petter Selasky 		mtx_lock(chan->pcm_mtx);
140431070b5bSHans Petter Selasky 		chan->cur_alt = CHAN_MAX_ALT;
1405455a367fSHans Petter Selasky 		mtx_unlock(chan->pcm_mtx);
1406455a367fSHans Petter Selasky 
140731070b5bSHans Petter Selasky 		/*
140831070b5bSHans Petter Selasky 		 * The first alternate setting is typically used for
140931070b5bSHans Petter Selasky 		 * power saving mode. Set this alternate setting as
141031070b5bSHans Petter Selasky 		 * part of entering stop.
141131070b5bSHans Petter Selasky 		 */
141231070b5bSHans Petter Selasky 		err = usbd_set_alt_interface_index(sc->sc_udev, chan->iface_index, 0);
141331070b5bSHans Petter Selasky 		if (err) {
141431070b5bSHans Petter Selasky 			DPRINTF("setting of default alternate index failed: %s!\n",
141531070b5bSHans Petter Selasky 			    usbd_errstr(err));
141631070b5bSHans Petter Selasky 		}
141731070b5bSHans Petter Selasky 		return;
1418455a367fSHans Petter Selasky 
141931070b5bSHans Petter Selasky 	case CHAN_OP_START:
142031070b5bSHans Petter Selasky 		/* Unsetup prior USB transfers, if any. */
1421455a367fSHans Petter Selasky 		usbd_transfer_unsetup(chan->xfer, UAUDIO_NCHANBUFS + 1);
142231070b5bSHans Petter Selasky 		break;
142331070b5bSHans Petter Selasky 
142431070b5bSHans Petter Selasky 	default:
142531070b5bSHans Petter Selasky 		return;
142631070b5bSHans Petter Selasky 	}
142731070b5bSHans Petter Selasky 
142831070b5bSHans Petter Selasky 	mtx_lock(chan->pcm_mtx);
142931070b5bSHans Petter Selasky 	next_alt = chan->set_alt;
143031070b5bSHans Petter Selasky 	mtx_unlock(chan->pcm_mtx);
143131070b5bSHans Petter Selasky 
143231070b5bSHans Petter Selasky 	chan_alt = chan->usb_alt + next_alt;
1433455a367fSHans Petter Selasky 
1434455a367fSHans Petter Selasky 	err = usbd_set_alt_interface_index(sc->sc_udev,
1435455a367fSHans Petter Selasky 	    chan_alt->iface_index, chan_alt->iface_alt_index);
1436455a367fSHans Petter Selasky 	if (err) {
1437455a367fSHans Petter Selasky 		DPRINTF("setting of alternate index failed: %s!\n",
1438455a367fSHans Petter Selasky 		    usbd_errstr(err));
1439455a367fSHans Petter Selasky 		goto error;
1440455a367fSHans Petter Selasky 	}
1441455a367fSHans Petter Selasky 
1442455a367fSHans Petter Selasky 	/*
1443455a367fSHans Petter Selasky 	 * Only set the sample rate if the channel reports that it
1444455a367fSHans Petter Selasky 	 * supports the frequency control.
1445455a367fSHans Petter Selasky 	 */
1446455a367fSHans Petter Selasky 
1447455a367fSHans Petter Selasky 	if (sc->sc_audio_rev >= UAUDIO_VERSION_30) {
1448455a367fSHans Petter Selasky 		/* FALLTHROUGH */
1449455a367fSHans Petter Selasky 	} else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) {
1450455a367fSHans Petter Selasky 		unsigned int x;
1451455a367fSHans Petter Selasky 
1452455a367fSHans Petter Selasky 		for (x = 0; x != 256; x++) {
1453455a367fSHans Petter Selasky 			if (dir == PCMDIR_PLAY) {
1454455a367fSHans Petter Selasky 				if (!(sc->sc_mixer_clocks.bit_output[x / 8] &
1455455a367fSHans Petter Selasky 				    (1 << (x % 8)))) {
1456455a367fSHans Petter Selasky 					continue;
1457455a367fSHans Petter Selasky 				}
1458455a367fSHans Petter Selasky 			} else {
1459455a367fSHans Petter Selasky 				if (!(sc->sc_mixer_clocks.bit_input[x / 8] &
1460455a367fSHans Petter Selasky 				    (1 << (x % 8)))) {
1461455a367fSHans Petter Selasky 					continue;
1462455a367fSHans Petter Selasky 				}
1463455a367fSHans Petter Selasky 			}
1464455a367fSHans Petter Selasky 
1465455a367fSHans Petter Selasky 			if (uaudio20_set_speed(sc->sc_udev,
1466455a367fSHans Petter Selasky 			    sc->sc_mixer_iface_no, x, chan_alt->sample_rate)) {
1467455a367fSHans Petter Selasky 				/*
1468455a367fSHans Petter Selasky 				 * If the endpoint is adaptive setting
1469455a367fSHans Petter Selasky 				 * the speed may fail.
1470455a367fSHans Petter Selasky 				 */
1471455a367fSHans Petter Selasky 				DPRINTF("setting of sample rate failed! "
1472455a367fSHans Petter Selasky 				    "(continuing anyway)\n");
1473455a367fSHans Petter Selasky 			}
1474455a367fSHans Petter Selasky 		}
1475455a367fSHans Petter Selasky 	} else if (chan_alt->p_sed.v1->bmAttributes & UA_SED_FREQ_CONTROL) {
1476455a367fSHans Petter Selasky 		if (uaudio_set_speed(sc->sc_udev,
1477455a367fSHans Petter Selasky 		    chan_alt->p_ed1->bEndpointAddress, chan_alt->sample_rate)) {
1478455a367fSHans Petter Selasky 			/*
1479455a367fSHans Petter Selasky 			 * If the endpoint is adaptive setting the
1480455a367fSHans Petter Selasky 			 * speed may fail.
1481455a367fSHans Petter Selasky 			 */
1482455a367fSHans Petter Selasky 			DPRINTF("setting of sample rate failed! "
1483455a367fSHans Petter Selasky 			    "(continuing anyway)\n");
1484455a367fSHans Petter Selasky 		}
1485455a367fSHans Petter Selasky 	}
1486455a367fSHans Petter Selasky 	if (usbd_transfer_setup(sc->sc_udev, &chan_alt->iface_index, chan->xfer,
1487455a367fSHans Petter Selasky 	    chan_alt->usb_cfg, UAUDIO_NCHANBUFS + 1, chan, chan->pcm_mtx)) {
1488455a367fSHans Petter Selasky 		DPRINTF("could not allocate USB transfers!\n");
1489455a367fSHans Petter Selasky 		goto error;
1490455a367fSHans Petter Selasky 	}
1491455a367fSHans Petter Selasky 
1492455a367fSHans Petter Selasky 	fps = usbd_get_isoc_fps(sc->sc_udev);
1493455a367fSHans Petter Selasky 
1494455a367fSHans Petter Selasky 	if (fps < 8000) {
1495455a367fSHans Petter Selasky 		/* FULL speed USB */
14964d76235fSHans Petter Selasky 		frames = uaudio_buffer_ms;
1497455a367fSHans Petter Selasky 	} else {
1498455a367fSHans Petter Selasky 		/* HIGH speed USB */
14994d76235fSHans Petter Selasky 		frames = uaudio_buffer_ms * 8;
1500455a367fSHans Petter Selasky 	}
1501455a367fSHans Petter Selasky 
1502455a367fSHans Petter Selasky 	fps_shift = usbd_xfer_get_fps_shift(chan->xfer[0]);
1503455a367fSHans Petter Selasky 
1504455a367fSHans Petter Selasky 	/* down shift number of frames per second, if any */
1505455a367fSHans Petter Selasky 	fps >>= fps_shift;
1506455a367fSHans Petter Selasky 	frames >>= fps_shift;
1507455a367fSHans Petter Selasky 
1508455a367fSHans Petter Selasky 	/* bytes per frame should not be zero */
1509455a367fSHans Petter Selasky 	chan->bytes_per_frame[0] =
1510455a367fSHans Petter Selasky 	    ((chan_alt->sample_rate / fps) * chan_alt->sample_size);
1511057b4402SPedro F. Giffuni 	chan->bytes_per_frame[1] = howmany(chan_alt->sample_rate, fps) *
1512057b4402SPedro F. Giffuni 	    chan_alt->sample_size;
1513455a367fSHans Petter Selasky 
1514455a367fSHans Petter Selasky 	/* setup data rate dithering, if any */
1515455a367fSHans Petter Selasky 	chan->frames_per_second = fps;
1516455a367fSHans Petter Selasky 	chan->sample_rem = chan_alt->sample_rate % fps;
1517455a367fSHans Petter Selasky 	chan->sample_curr = 0;
1518455a367fSHans Petter Selasky 
1519455a367fSHans Petter Selasky 	/* compute required buffer size */
1520455a367fSHans Petter Selasky 	buf_size = (chan->bytes_per_frame[1] * frames);
1521455a367fSHans Petter Selasky 
1522455a367fSHans Petter Selasky 	if (buf_size > (chan->end - chan->start)) {
1523455a367fSHans Petter Selasky 		DPRINTF("buffer size is too big\n");
1524455a367fSHans Petter Selasky 		goto error;
1525455a367fSHans Petter Selasky 	}
1526455a367fSHans Petter Selasky 
1527455a367fSHans Petter Selasky 	chan->intr_frames = frames;
1528455a367fSHans Petter Selasky 
1529455a367fSHans Petter Selasky 	DPRINTF("fps=%d sample_rem=%d\n", (int)fps, (int)chan->sample_rem);
1530455a367fSHans Petter Selasky 
1531455a367fSHans Petter Selasky 	if (chan->intr_frames == 0) {
1532455a367fSHans Petter Selasky 		DPRINTF("frame shift is too high!\n");
1533455a367fSHans Petter Selasky 		goto error;
1534455a367fSHans Petter Selasky 	}
1535455a367fSHans Petter Selasky 
1536455a367fSHans Petter Selasky #if (UAUDIO_NCHANBUFS != 2)
153731070b5bSHans Petter Selasky #error "Please update code below!"
1538455a367fSHans Petter Selasky #endif
153931070b5bSHans Petter Selasky 
1540455a367fSHans Petter Selasky 	mtx_lock(chan->pcm_mtx);
154131070b5bSHans Petter Selasky 	chan->cur_alt = next_alt;
1542455a367fSHans Petter Selasky 	usbd_transfer_start(chan->xfer[0]);
1543455a367fSHans Petter Selasky 	usbd_transfer_start(chan->xfer[1]);
1544455a367fSHans Petter Selasky 	mtx_unlock(chan->pcm_mtx);
1545455a367fSHans Petter Selasky 	return;
1546455a367fSHans Petter Selasky error:
1547455a367fSHans Petter Selasky 	usbd_transfer_unsetup(chan->xfer, UAUDIO_NCHANBUFS + 1);
1548455a367fSHans Petter Selasky 
1549455a367fSHans Petter Selasky 	mtx_lock(chan->pcm_mtx);
1550455a367fSHans Petter Selasky 	chan->cur_alt = CHAN_MAX_ALT;
1551455a367fSHans Petter Selasky 	mtx_unlock(chan->pcm_mtx);
1552455a367fSHans Petter Selasky }
1553455a367fSHans Petter Selasky 
1554455a367fSHans Petter Selasky static void
uaudio_configure_msg(struct usb_proc_msg * pm)1555455a367fSHans Petter Selasky uaudio_configure_msg(struct usb_proc_msg *pm)
1556455a367fSHans Petter Selasky {
1557455a367fSHans Petter Selasky 	struct uaudio_softc *sc = ((struct uaudio_configure_msg *)pm)->sc;
155886c9b3f3SHans Petter Selasky 	unsigned i;
1559455a367fSHans Petter Selasky 
1560455a367fSHans Petter Selasky 	usb_proc_explore_unlock(sc->sc_udev);
156186c9b3f3SHans Petter Selasky 	for (i = 0; i != UAUDIO_MAX_CHILD; i++) {
156286c9b3f3SHans Petter Selasky 		uaudio_configure_msg_sub(sc, &sc->sc_play_chan[i], PCMDIR_PLAY);
156386c9b3f3SHans Petter Selasky 		uaudio_configure_msg_sub(sc, &sc->sc_rec_chan[i], PCMDIR_REC);
156486c9b3f3SHans Petter Selasky 	}
1565455a367fSHans Petter Selasky 	usb_proc_explore_lock(sc->sc_udev);
1566455a367fSHans Petter Selasky }
1567455a367fSHans Petter Selasky 
15683a3f90c6SAndrew Thompson /*========================================================================*
15693a3f90c6SAndrew Thompson  * AS - Audio Stream - routines
15703a3f90c6SAndrew Thompson  *========================================================================*/
15713a3f90c6SAndrew Thompson 
1572b850ecc1SAndrew Thompson #ifdef USB_DEBUG
15733a3f90c6SAndrew Thompson static void
uaudio_chan_dump_ep_desc(const usb_endpoint_descriptor_audio_t * ed)15744c21be9bSRebecca Cran uaudio_chan_dump_ep_desc(const usb_endpoint_descriptor_audio_t *ed)
15753a3f90c6SAndrew Thompson {
15763a3f90c6SAndrew Thompson 	if (ed) {
15773a3f90c6SAndrew Thompson 		DPRINTF("endpoint=%p bLength=%d bDescriptorType=%d \n"
15783a3f90c6SAndrew Thompson 		    "bEndpointAddress=%d bmAttributes=0x%x \n"
15793a3f90c6SAndrew Thompson 		    "wMaxPacketSize=%d bInterval=%d \n"
15803a3f90c6SAndrew Thompson 		    "bRefresh=%d bSynchAddress=%d\n",
15813a3f90c6SAndrew Thompson 		    ed, ed->bLength, ed->bDescriptorType,
15823a3f90c6SAndrew Thompson 		    ed->bEndpointAddress, ed->bmAttributes,
15833a3f90c6SAndrew Thompson 		    UGETW(ed->wMaxPacketSize), ed->bInterval,
1584e3e05e50SAndrew Thompson 		    UEP_HAS_REFRESH(ed) ? ed->bRefresh : 0,
1585e3e05e50SAndrew Thompson 		    UEP_HAS_SYNCADDR(ed) ? ed->bSynchAddress : 0);
15863a3f90c6SAndrew Thompson 	}
15873a3f90c6SAndrew Thompson }
15883a3f90c6SAndrew Thompson 
15893a3f90c6SAndrew Thompson #endif
15903a3f90c6SAndrew Thompson 
1591f895cc0cSHans Petter Selasky /*
1592f895cc0cSHans Petter Selasky  * The following is a workaround for broken no-name USB audio devices
1593f895cc0cSHans Petter Selasky  * sold by dealextreme called "3D sound". The problem is that the
1594f895cc0cSHans Petter Selasky  * manufacturer computed wMaxPacketSize is too small to hold the
1595f895cc0cSHans Petter Selasky  * actual data sent. In other words the device sometimes sends more
1596f895cc0cSHans Petter Selasky  * data than it actually reports it can send in a single isochronous
1597f895cc0cSHans Petter Selasky  * packet.
1598f895cc0cSHans Petter Selasky  */
1599f895cc0cSHans Petter Selasky static void
uaudio_record_fix_fs(usb_endpoint_descriptor_audio_t * ep,uint32_t xps,uint32_t add)1600f895cc0cSHans Petter Selasky uaudio_record_fix_fs(usb_endpoint_descriptor_audio_t *ep,
1601f895cc0cSHans Petter Selasky     uint32_t xps, uint32_t add)
1602f895cc0cSHans Petter Selasky {
1603f895cc0cSHans Petter Selasky 	uint32_t mps;
1604f895cc0cSHans Petter Selasky 
1605f895cc0cSHans Petter Selasky 	mps = UGETW(ep->wMaxPacketSize);
1606f895cc0cSHans Petter Selasky 
1607f895cc0cSHans Petter Selasky 	/*
1608f895cc0cSHans Petter Selasky 	 * If the device indicates it can send more data than what the
1609f895cc0cSHans Petter Selasky 	 * sample rate indicates, we apply the workaround.
1610f895cc0cSHans Petter Selasky 	 */
1611f895cc0cSHans Petter Selasky 	if (mps > xps) {
1612f895cc0cSHans Petter Selasky 		/* allow additional data */
1613f895cc0cSHans Petter Selasky 		xps += add;
1614f895cc0cSHans Petter Selasky 
1615f895cc0cSHans Petter Selasky 		/* check against the maximum USB 1.x length */
1616f895cc0cSHans Petter Selasky 		if (xps > 1023)
1617f895cc0cSHans Petter Selasky 			xps = 1023;
1618f895cc0cSHans Petter Selasky 
1619f895cc0cSHans Petter Selasky 		/* check if we should do an update */
1620f895cc0cSHans Petter Selasky 		if (mps < xps) {
1621f895cc0cSHans Petter Selasky 			/* simply update the wMaxPacketSize field */
1622f895cc0cSHans Petter Selasky 			USETW(ep->wMaxPacketSize, xps);
1623f895cc0cSHans Petter Selasky 			DPRINTF("Workaround: Updated wMaxPacketSize "
1624f895cc0cSHans Petter Selasky 			    "from %d to %d bytes.\n",
1625f895cc0cSHans Petter Selasky 			    (int)mps, (int)xps);
1626f895cc0cSHans Petter Selasky 		}
1627f895cc0cSHans Petter Selasky 	}
1628f895cc0cSHans Petter Selasky }
1629f895cc0cSHans Petter Selasky 
1630e2524b2eSHans Petter Selasky static usb_error_t
uaudio20_check_rate(struct usb_device * udev,uint8_t iface_no,uint8_t clockid,uint32_t rate)1631e2524b2eSHans Petter Selasky uaudio20_check_rate(struct usb_device *udev, uint8_t iface_no,
1632e2524b2eSHans Petter Selasky     uint8_t clockid, uint32_t rate)
1633e2524b2eSHans Petter Selasky {
1634e2524b2eSHans Petter Selasky 	struct usb_device_request req;
1635e2524b2eSHans Petter Selasky 	usb_error_t error;
16363d6ed119SGordon Bergling #define	UAUDIO20_MAX_RATES 32	/* we support at maximum 32 rates */
1637a89c8065SHans Petter Selasky 	uint8_t data[2 + UAUDIO20_MAX_RATES * 12];
1638e2524b2eSHans Petter Selasky 	uint16_t actlen;
1639e2524b2eSHans Petter Selasky 	uint16_t rates;
1640e2524b2eSHans Petter Selasky 	uint16_t x;
1641e2524b2eSHans Petter Selasky 
1642e2524b2eSHans Petter Selasky 	DPRINTFN(6, "ifaceno=%d clockid=%d rate=%u\n",
1643e2524b2eSHans Petter Selasky 	    iface_no, clockid, rate);
1644e2524b2eSHans Petter Selasky 
1645e2524b2eSHans Petter Selasky 	req.bmRequestType = UT_READ_CLASS_INTERFACE;
1646e2524b2eSHans Petter Selasky 	req.bRequest = UA20_CS_RANGE;
1647e2524b2eSHans Petter Selasky 	USETW2(req.wValue, UA20_CS_SAM_FREQ_CONTROL, 0);
1648e2524b2eSHans Petter Selasky 	USETW2(req.wIndex, clockid, iface_no);
1649a89c8065SHans Petter Selasky 	/*
1650a89c8065SHans Petter Selasky 	 * Assume there is at least one rate to begin with, else some
1651a89c8065SHans Petter Selasky 	 * devices might refuse to return the USB descriptor:
1652a89c8065SHans Petter Selasky 	 */
1653a89c8065SHans Petter Selasky 	USETW(req.wLength, (2 + 1 * 12));
1654a89c8065SHans Petter Selasky 
1655a89c8065SHans Petter Selasky 	error = usbd_do_request_flags(udev, NULL, &req, data,
1656a89c8065SHans Petter Selasky 	    USB_SHORT_XFER_OK, &actlen, USB_DEFAULT_TIMEOUT);
1657a89c8065SHans Petter Selasky 
1658a89c8065SHans Petter Selasky 	if (error != 0 || actlen < 2) {
1659a89c8065SHans Petter Selasky 		/*
1660a89c8065SHans Petter Selasky 		 * Likely the descriptor doesn't fit into the supplied
1661a89c8065SHans Petter Selasky 		 * buffer. Try using a larger buffer and see if that
1662a89c8065SHans Petter Selasky 		 * helps:
1663a89c8065SHans Petter Selasky 		 */
1664a89c8065SHans Petter Selasky 		rates = MIN(UAUDIO20_MAX_RATES, (255 - 2) / 12);
1665a89c8065SHans Petter Selasky 		error = USB_ERR_INVAL;
1666a89c8065SHans Petter Selasky 	} else {
1667a89c8065SHans Petter Selasky 		rates = UGETW(data);
1668a89c8065SHans Petter Selasky 
1669a89c8065SHans Petter Selasky 		if (rates > UAUDIO20_MAX_RATES) {
1670a89c8065SHans Petter Selasky 			DPRINTF("Too many rates truncating to %d\n", UAUDIO20_MAX_RATES);
1671a89c8065SHans Petter Selasky 			rates = UAUDIO20_MAX_RATES;
1672a89c8065SHans Petter Selasky 			error = USB_ERR_INVAL;
1673a89c8065SHans Petter Selasky 		} else if (rates > 1) {
1674a89c8065SHans Petter Selasky 			DPRINTF("Need to read full rate descriptor\n");
1675a89c8065SHans Petter Selasky 			error = USB_ERR_INVAL;
1676a89c8065SHans Petter Selasky 		}
1677a89c8065SHans Petter Selasky 	}
1678a89c8065SHans Petter Selasky 
1679a89c8065SHans Petter Selasky 	if (error != 0) {
1680a89c8065SHans Petter Selasky 		/*
1681a89c8065SHans Petter Selasky 		 * Try to read full rate descriptor.
1682a89c8065SHans Petter Selasky 		 */
1683a89c8065SHans Petter Selasky 		actlen = (2 + rates * 12);
1684a89c8065SHans Petter Selasky 
1685a89c8065SHans Petter Selasky 		USETW(req.wLength, actlen);
1686e2524b2eSHans Petter Selasky 
1687e2524b2eSHans Petter Selasky 	        error = usbd_do_request_flags(udev, NULL, &req, data,
1688e2524b2eSHans Petter Selasky 		    USB_SHORT_XFER_OK, &actlen, USB_DEFAULT_TIMEOUT);
1689e2524b2eSHans Petter Selasky 
1690e2524b2eSHans Petter Selasky 		if (error != 0 || actlen < 2)
1691e2524b2eSHans Petter Selasky 			return (USB_ERR_INVAL);
1692e2524b2eSHans Petter Selasky 
1693a89c8065SHans Petter Selasky 		rates = UGETW(data);
1694a89c8065SHans Petter Selasky 	}
1695a89c8065SHans Petter Selasky 
1696e2524b2eSHans Petter Selasky 	actlen = (actlen - 2) / 12;
1697e2524b2eSHans Petter Selasky 
1698e2524b2eSHans Petter Selasky 	if (rates > actlen) {
1699a89c8065SHans Petter Selasky 		DPRINTF("Too many rates truncating to %d\n", actlen);
1700e2524b2eSHans Petter Selasky 		rates = actlen;
1701e2524b2eSHans Petter Selasky 	}
1702e2524b2eSHans Petter Selasky 
1703e2524b2eSHans Petter Selasky 	for (x = 0; x != rates; x++) {
1704e2524b2eSHans Petter Selasky 		uint32_t min = UGETDW(data + 2 + (12 * x));
1705e2524b2eSHans Petter Selasky 		uint32_t max = UGETDW(data + 6 + (12 * x));
1706e2524b2eSHans Petter Selasky 		uint32_t res = UGETDW(data + 10 + (12 * x));
1707e2524b2eSHans Petter Selasky 
1708e2524b2eSHans Petter Selasky 		if (res == 0) {
1709e2524b2eSHans Petter Selasky 			DPRINTF("Zero residue\n");
1710e2524b2eSHans Petter Selasky 			res = 1;
1711e2524b2eSHans Petter Selasky 		}
1712e2524b2eSHans Petter Selasky 
1713e2524b2eSHans Petter Selasky 		if (min > max) {
1714e2524b2eSHans Petter Selasky 			DPRINTF("Swapped max and min\n");
1715e2524b2eSHans Petter Selasky 			uint32_t temp;
1716e2524b2eSHans Petter Selasky 			temp = min;
1717e2524b2eSHans Petter Selasky 			min = max;
1718e2524b2eSHans Petter Selasky 			max = temp;
1719e2524b2eSHans Petter Selasky 		}
1720e2524b2eSHans Petter Selasky 
1721e2524b2eSHans Petter Selasky 		if (rate >= min && rate <= max &&
1722e2524b2eSHans Petter Selasky 		    (((rate - min) % res) == 0)) {
1723e2524b2eSHans Petter Selasky 			return (0);
1724e2524b2eSHans Petter Selasky 		}
1725e2524b2eSHans Petter Selasky 	}
1726e2524b2eSHans Petter Selasky 	return (USB_ERR_INVAL);
1727e2524b2eSHans Petter Selasky }
1728e2524b2eSHans Petter Selasky 
172986c9b3f3SHans Petter Selasky static struct uaudio_chan *
uaudio_get_chan(struct uaudio_softc * sc,struct uaudio_chan * chan,uint8_t iface_index)173086c9b3f3SHans Petter Selasky uaudio_get_chan(struct uaudio_softc *sc, struct uaudio_chan *chan,
173186c9b3f3SHans Petter Selasky     uint8_t iface_index)
173286c9b3f3SHans Petter Selasky {
173386c9b3f3SHans Petter Selasky 	unsigned i;
173486c9b3f3SHans Petter Selasky 
173586c9b3f3SHans Petter Selasky 	for (i = 0; i != UAUDIO_MAX_CHILD; i++, chan++) {
173686c9b3f3SHans Petter Selasky 		if (chan->num_alt == 0) {
173786c9b3f3SHans Petter Selasky 			chan->iface_index = iface_index;
173886c9b3f3SHans Petter Selasky 			return (chan);
173986c9b3f3SHans Petter Selasky 		} else if (chan->iface_index == iface_index)
174086c9b3f3SHans Petter Selasky 			return (chan);
174186c9b3f3SHans Petter Selasky 	}
174286c9b3f3SHans Petter Selasky 	return (NULL);
174386c9b3f3SHans Petter Selasky }
174486c9b3f3SHans Petter Selasky 
17453a3f90c6SAndrew Thompson static void
uaudio_chan_fill_info_sub(struct uaudio_softc * sc,struct usb_device * udev,uint32_t rate,uint8_t channels,uint8_t bit_resolution)1746760bc48eSAndrew Thompson uaudio_chan_fill_info_sub(struct uaudio_softc *sc, struct usb_device *udev,
1747afbfddd9SAndrew Thompson     uint32_t rate, uint8_t channels, uint8_t bit_resolution)
17483a3f90c6SAndrew Thompson {
1749760bc48eSAndrew Thompson 	struct usb_descriptor *desc = NULL;
1750e2524b2eSHans Petter Selasky 	union uaudio_asid asid = { NULL };
1751e2524b2eSHans Petter Selasky 	union uaudio_asf1d asf1d = { NULL };
1752e2524b2eSHans Petter Selasky 	union uaudio_sed sed = { NULL };
17538bf51ab5SHans Petter Selasky 	struct usb_midi_streaming_endpoint_descriptor *msid = NULL;
1754f895cc0cSHans Petter Selasky 	usb_endpoint_descriptor_audio_t *ed1 = NULL;
1755e2524b2eSHans Petter Selasky 	const struct usb_audio_control_descriptor *acdp = NULL;
1756a593f6b8SAndrew Thompson 	struct usb_config_descriptor *cd = usbd_get_config_descriptor(udev);
1757760bc48eSAndrew Thompson 	struct usb_interface_descriptor *id;
1758e2524b2eSHans Petter Selasky 	const struct uaudio_format *p_fmt = NULL;
17593a3f90c6SAndrew Thompson 	struct uaudio_chan *chan;
1760455a367fSHans Petter Selasky 	struct uaudio_chan_alt *chan_alt;
1761455a367fSHans Petter Selasky 	uint32_t format;
17623a3f90c6SAndrew Thompson 	uint16_t curidx = 0xFFFF;
17633a3f90c6SAndrew Thompson 	uint16_t lastidx = 0xFFFF;
17643a3f90c6SAndrew Thompson 	uint16_t alt_index = 0;
1765e2524b2eSHans Petter Selasky 	uint16_t audio_rev = 0;
1766e2524b2eSHans Petter Selasky 	uint16_t x;
17673a3f90c6SAndrew Thompson 	uint8_t ep_dir;
17683a3f90c6SAndrew Thompson 	uint8_t bChannels;
17693a3f90c6SAndrew Thompson 	uint8_t bBitResolution;
17703a3f90c6SAndrew Thompson 	uint8_t audio_if = 0;
17718bf51ab5SHans Petter Selasky 	uint8_t midi_if = 0;
177225b74dabSHans Petter Selasky 	uint8_t uma_if_class;
17733a3f90c6SAndrew Thompson 
1774a593f6b8SAndrew Thompson 	while ((desc = usb_desc_foreach(cd, desc))) {
17753a3f90c6SAndrew Thompson 		if ((desc->bDescriptorType == UDESC_INTERFACE) &&
17763a3f90c6SAndrew Thompson 		    (desc->bLength >= sizeof(*id))) {
17773a3f90c6SAndrew Thompson 			id = (void *)desc;
17783a3f90c6SAndrew Thompson 
17793a3f90c6SAndrew Thompson 			if (id->bInterfaceNumber != lastidx) {
17803a3f90c6SAndrew Thompson 				lastidx = id->bInterfaceNumber;
17813a3f90c6SAndrew Thompson 				curidx++;
17823a3f90c6SAndrew Thompson 				alt_index = 0;
17833a3f90c6SAndrew Thompson 
17843a3f90c6SAndrew Thompson 			} else {
17853a3f90c6SAndrew Thompson 				alt_index++;
17863a3f90c6SAndrew Thompson 			}
17873a3f90c6SAndrew Thompson 
178876b71212SHans Petter Selasky 			if ((!(sc->sc_hid.flags & UAUDIO_HID_VALID)) &&
178976b71212SHans Petter Selasky 			    (id->bInterfaceClass == UICLASS_HID) &&
179076b71212SHans Petter Selasky 			    (id->bInterfaceSubClass == 0) &&
179176b71212SHans Petter Selasky 			    (id->bInterfaceProtocol == 0) &&
179276b71212SHans Petter Selasky 			    (alt_index == 0) &&
179376b71212SHans Petter Selasky 			    usbd_get_iface(udev, curidx) != NULL) {
179476b71212SHans Petter Selasky 				DPRINTF("Found HID interface at %d\n",
179576b71212SHans Petter Selasky 				    curidx);
179676b71212SHans Petter Selasky 				sc->sc_hid.flags |= UAUDIO_HID_VALID;
179776b71212SHans Petter Selasky 				sc->sc_hid.iface_index = curidx;
179876b71212SHans Petter Selasky 			}
179976b71212SHans Petter Selasky 
180025b74dabSHans Petter Selasky 			uma_if_class =
180125b74dabSHans Petter Selasky 			    ((id->bInterfaceClass == UICLASS_AUDIO) ||
180225b74dabSHans Petter Selasky 			    ((id->bInterfaceClass == UICLASS_VENDOR) &&
180325b74dabSHans Petter Selasky 			    (sc->sc_uq_au_vendor_class != 0)));
180425b74dabSHans Petter Selasky 
18058bf51ab5SHans Petter Selasky 			if ((uma_if_class != 0) &&
18068bf51ab5SHans Petter Selasky 			    (id->bInterfaceSubClass == UISUBCLASS_AUDIOSTREAM)) {
18073a3f90c6SAndrew Thompson 				audio_if = 1;
18083a3f90c6SAndrew Thompson 			} else {
18093a3f90c6SAndrew Thompson 				audio_if = 0;
18103a3f90c6SAndrew Thompson 			}
18113a3f90c6SAndrew Thompson 
181225b74dabSHans Petter Selasky 			if ((uma_if_class != 0) &&
18133a3f90c6SAndrew Thompson 			    (id->bInterfaceSubClass == UISUBCLASS_MIDISTREAM)) {
18143a3f90c6SAndrew Thompson 				/*
18153a3f90c6SAndrew Thompson 				 * XXX could allow multiple MIDI interfaces
18163a3f90c6SAndrew Thompson 				 */
18178bf51ab5SHans Petter Selasky 				midi_if = 1;
18183a3f90c6SAndrew Thompson 
18193a3f90c6SAndrew Thompson 				if ((sc->sc_midi_chan.valid == 0) &&
18208bf51ab5SHans Petter Selasky 				    (usbd_get_iface(udev, curidx) != NULL)) {
18213a3f90c6SAndrew Thompson 					sc->sc_midi_chan.iface_index = curidx;
18223a3f90c6SAndrew Thompson 					sc->sc_midi_chan.iface_alt_index = alt_index;
18233a3f90c6SAndrew Thompson 					sc->sc_midi_chan.valid = 1;
18243a3f90c6SAndrew Thompson 				}
18258bf51ab5SHans Petter Selasky 			} else {
18268bf51ab5SHans Petter Selasky 				midi_if = 0;
18273a3f90c6SAndrew Thompson 			}
1828e2524b2eSHans Petter Selasky 			asid.v1 = NULL;
1829e2524b2eSHans Petter Selasky 			asf1d.v1 = NULL;
18303a3f90c6SAndrew Thompson 			ed1 = NULL;
1831e2524b2eSHans Petter Selasky 			sed.v1 = NULL;
18328c20de92SHans Petter Selasky 
18338c20de92SHans Petter Selasky 			/*
18348c20de92SHans Petter Selasky 			 * There can only be one USB audio instance
18358c20de92SHans Petter Selasky 			 * per USB device. Grab all USB audio
18368c20de92SHans Petter Selasky 			 * interfaces on this USB device so that we
18378c20de92SHans Petter Selasky 			 * don't attach USB audio twice:
18388c20de92SHans Petter Selasky 			 */
18398c20de92SHans Petter Selasky 			if (alt_index == 0 && curidx != sc->sc_mixer_iface_index &&
18408c20de92SHans Petter Selasky 			    (id->bInterfaceClass == UICLASS_AUDIO || audio_if != 0 ||
18418c20de92SHans Petter Selasky 			    midi_if != 0)) {
18428c20de92SHans Petter Selasky 				usbd_set_parent_iface(sc->sc_udev, curidx,
18438c20de92SHans Petter Selasky 				    sc->sc_mixer_iface_index);
18448c20de92SHans Petter Selasky 			}
18453a3f90c6SAndrew Thompson 		}
1846e2524b2eSHans Petter Selasky 
18471234097eSHans Petter Selasky 		if (audio_if == 0) {
18488bf51ab5SHans Petter Selasky 			if (midi_if == 0) {
1849e2524b2eSHans Petter Selasky 				if ((acdp == NULL) &&
1850e2524b2eSHans Petter Selasky 				    (desc->bDescriptorType == UDESC_CS_INTERFACE) &&
1851e2524b2eSHans Petter Selasky 				    (desc->bDescriptorSubtype == UDESCSUB_AC_HEADER) &&
1852e2524b2eSHans Petter Selasky 				    (desc->bLength >= sizeof(*acdp))) {
1853e2524b2eSHans Petter Selasky 					acdp = (void *)desc;
1854e2524b2eSHans Petter Selasky 					audio_rev = UGETW(acdp->bcdADC);
1855e2524b2eSHans Petter Selasky 				}
18568bf51ab5SHans Petter Selasky 			} else {
18578bf51ab5SHans Petter Selasky 				msid = (void *)desc;
1858e2524b2eSHans Petter Selasky 
18598bf51ab5SHans Petter Selasky 				/* get the maximum number of embedded jacks in use, if any */
18608bf51ab5SHans Petter Selasky 				if (msid->bLength >= sizeof(*msid) &&
18618bf51ab5SHans Petter Selasky 				    msid->bDescriptorType == UDESC_CS_ENDPOINT &&
18628bf51ab5SHans Petter Selasky 				    msid->bDescriptorSubtype == MS_GENERAL &&
18638bf51ab5SHans Petter Selasky 				    msid->bNumEmbMIDIJack > sc->sc_midi_chan.max_emb_jack) {
18648bf51ab5SHans Petter Selasky 					sc->sc_midi_chan.max_emb_jack = msid->bNumEmbMIDIJack;
18658bf51ab5SHans Petter Selasky 				}
18668bf51ab5SHans Petter Selasky 			}
18671234097eSHans Petter Selasky 			/*
18681234097eSHans Petter Selasky 			 * Don't collect any USB audio descriptors if
18691234097eSHans Petter Selasky 			 * this is not an USB audio stream interface.
18701234097eSHans Petter Selasky 			 */
18711234097eSHans Petter Selasky 			continue;
18721234097eSHans Petter Selasky 		}
18731234097eSHans Petter Selasky 
1874f4dbf0d8SHans Petter Selasky 		if ((acdp != NULL || sc->sc_uq_au_vendor_class != 0) &&
1875e2524b2eSHans Petter Selasky 		    (desc->bDescriptorType == UDESC_CS_INTERFACE) &&
1876e2524b2eSHans Petter Selasky 		    (desc->bDescriptorSubtype == AS_GENERAL) &&
1877e2524b2eSHans Petter Selasky 		    (asid.v1 == NULL)) {
1878e2524b2eSHans Petter Selasky 			if (audio_rev >= UAUDIO_VERSION_30) {
1879e2524b2eSHans Petter Selasky 				/* FALLTHROUGH */
1880e2524b2eSHans Petter Selasky 			} else if (audio_rev >= UAUDIO_VERSION_20) {
1881e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*asid.v2)) {
1882e2524b2eSHans Petter Selasky 					asid.v2 = (void *)desc;
1883e2524b2eSHans Petter Selasky 				}
1884e2524b2eSHans Petter Selasky 			} else {
1885e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*asid.v1)) {
1886e2524b2eSHans Petter Selasky 					asid.v1 = (void *)desc;
18873a3f90c6SAndrew Thompson 				}
18883a3f90c6SAndrew Thompson 			}
1889e2524b2eSHans Petter Selasky 		}
1890f4dbf0d8SHans Petter Selasky 		if ((acdp != NULL || sc->sc_uq_au_vendor_class != 0) &&
1891e2524b2eSHans Petter Selasky 		    (desc->bDescriptorType == UDESC_CS_INTERFACE) &&
18923a3f90c6SAndrew Thompson 		    (desc->bDescriptorSubtype == FORMAT_TYPE) &&
1893e2524b2eSHans Petter Selasky 		    (asf1d.v1 == NULL)) {
1894e2524b2eSHans Petter Selasky 			if (audio_rev >= UAUDIO_VERSION_30) {
1895e2524b2eSHans Petter Selasky 				/* FALLTHROUGH */
1896e2524b2eSHans Petter Selasky 			} else if (audio_rev >= UAUDIO_VERSION_20) {
1897e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*asf1d.v2))
1898e2524b2eSHans Petter Selasky 					asf1d.v2 = (void *)desc;
1899e2524b2eSHans Petter Selasky 			} else {
1900e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*asf1d.v1)) {
1901e2524b2eSHans Petter Selasky 					asf1d.v1 = (void *)desc;
1902e2524b2eSHans Petter Selasky 
1903e2524b2eSHans Petter Selasky 					if (asf1d.v1->bFormatType != FORMAT_TYPE_I) {
19043a3f90c6SAndrew Thompson 						DPRINTFN(11, "ignored bFormatType = %d\n",
1905e2524b2eSHans Petter Selasky 						    asf1d.v1->bFormatType);
1906e2524b2eSHans Petter Selasky 						asf1d.v1 = NULL;
19073a3f90c6SAndrew Thompson 						continue;
19083a3f90c6SAndrew Thompson 					}
1909e2524b2eSHans Petter Selasky 					if (desc->bLength < (sizeof(*asf1d.v1) +
1910e2524b2eSHans Petter Selasky 					    ((asf1d.v1->bSamFreqType == 0) ? 6 :
1911e2524b2eSHans Petter Selasky 					    (asf1d.v1->bSamFreqType * 3)))) {
1912e2524b2eSHans Petter Selasky 						DPRINTFN(11, "invalid descriptor, "
1913e2524b2eSHans Petter Selasky 						    "too short\n");
1914e2524b2eSHans Petter Selasky 						asf1d.v1 = NULL;
19153a3f90c6SAndrew Thompson 						continue;
19163a3f90c6SAndrew Thompson 					}
19173a3f90c6SAndrew Thompson 				}
19183a3f90c6SAndrew Thompson 			}
1919e2524b2eSHans Petter Selasky 		}
19203a3f90c6SAndrew Thompson 		if ((desc->bDescriptorType == UDESC_ENDPOINT) &&
1921e2524b2eSHans Petter Selasky 		    (desc->bLength >= UEP_MINSIZE) &&
1922e2524b2eSHans Petter Selasky 		    (ed1 == NULL)) {
19233a3f90c6SAndrew Thompson 			ed1 = (void *)desc;
19243a3f90c6SAndrew Thompson 			if (UE_GET_XFERTYPE(ed1->bmAttributes) != UE_ISOCHRONOUS) {
19253a3f90c6SAndrew Thompson 				ed1 = NULL;
1926e2524b2eSHans Petter Selasky 				continue;
19273a3f90c6SAndrew Thompson 			}
19283a3f90c6SAndrew Thompson 		}
1929f4dbf0d8SHans Petter Selasky 		if ((acdp != NULL || sc->sc_uq_au_vendor_class != 0) &&
1930e2524b2eSHans Petter Selasky 		    (desc->bDescriptorType == UDESC_CS_ENDPOINT) &&
19313a3f90c6SAndrew Thompson 		    (desc->bDescriptorSubtype == AS_GENERAL) &&
1932e2524b2eSHans Petter Selasky 		    (sed.v1 == NULL)) {
1933e2524b2eSHans Petter Selasky 			if (audio_rev >= UAUDIO_VERSION_30) {
1934e2524b2eSHans Petter Selasky 				/* FALLTHROUGH */
1935e2524b2eSHans Petter Selasky 			} else if (audio_rev >= UAUDIO_VERSION_20) {
1936e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*sed.v2))
1937e2524b2eSHans Petter Selasky 					sed.v2 = (void *)desc;
1938e2524b2eSHans Petter Selasky 			} else {
1939e2524b2eSHans Petter Selasky 				if (desc->bLength >= sizeof(*sed.v1))
1940e2524b2eSHans Petter Selasky 					sed.v1 = (void *)desc;
19413a3f90c6SAndrew Thompson 			}
19423a3f90c6SAndrew Thompson 		}
19431234097eSHans Petter Selasky 		if (asid.v1 == NULL || asf1d.v1 == NULL ||
19441234097eSHans Petter Selasky 		    ed1 == NULL || sed.v1 == NULL) {
1945e2524b2eSHans Petter Selasky 			/* need more descriptors */
1946e2524b2eSHans Petter Selasky 			continue;
1947e2524b2eSHans Petter Selasky 		}
19483a3f90c6SAndrew Thompson 
19493a3f90c6SAndrew Thompson 		ep_dir = UE_GET_DIR(ed1->bEndpointAddress);
19503a3f90c6SAndrew Thompson 
1951e3e05e50SAndrew Thompson 		/* We ignore sync endpoint information until further. */
19523a3f90c6SAndrew Thompson 
1953e2524b2eSHans Petter Selasky 		if (audio_rev >= UAUDIO_VERSION_30) {
1954e2524b2eSHans Petter Selasky 			goto next_ep;
1955e2524b2eSHans Petter Selasky 		} else if (audio_rev >= UAUDIO_VERSION_20) {
1956e2524b2eSHans Petter Selasky 			uint32_t dwFormat;
19573a3f90c6SAndrew Thompson 
1958e2524b2eSHans Petter Selasky 			dwFormat = UGETDW(asid.v2->bmFormats);
1959e2524b2eSHans Petter Selasky 			bChannels = asid.v2->bNrChannels;
1960f09566d3SHans Petter Selasky 			bBitResolution = asf1d.v2->bSubslotSize * 8;
1961e2524b2eSHans Petter Selasky 
1962e2524b2eSHans Petter Selasky 			if ((bChannels != channels) ||
1963e2524b2eSHans Petter Selasky 			    (bBitResolution != bit_resolution)) {
1964e2524b2eSHans Petter Selasky 				DPRINTF("Wrong number of channels\n");
1965e2524b2eSHans Petter Selasky 				goto next_ep;
1966e2524b2eSHans Petter Selasky 			}
1967e2524b2eSHans Petter Selasky 
1968e2524b2eSHans Petter Selasky 			for (p_fmt = uaudio20_formats;
1969e2524b2eSHans Petter Selasky 			    p_fmt->wFormat != 0; p_fmt++) {
1970e2524b2eSHans Petter Selasky 				if ((p_fmt->wFormat & dwFormat) &&
1971e2524b2eSHans Petter Selasky 				    (p_fmt->bPrecision == bBitResolution))
1972e2524b2eSHans Petter Selasky 					break;
1973e2524b2eSHans Petter Selasky 			}
1974e2524b2eSHans Petter Selasky 
1975e2524b2eSHans Petter Selasky 			if (p_fmt->wFormat == 0) {
1976e2524b2eSHans Petter Selasky 				DPRINTF("Unsupported audio format\n");
1977e2524b2eSHans Petter Selasky 				goto next_ep;
1978e2524b2eSHans Petter Selasky 			}
1979e2524b2eSHans Petter Selasky 
1980e2524b2eSHans Petter Selasky 			for (x = 0; x != 256; x++) {
1981e2524b2eSHans Petter Selasky 				if (ep_dir == UE_DIR_OUT) {
1982e2524b2eSHans Petter Selasky 					if (!(sc->sc_mixer_clocks.bit_output[x / 8] &
1983e2524b2eSHans Petter Selasky 					    (1 << (x % 8)))) {
1984e2524b2eSHans Petter Selasky 						continue;
19853a3f90c6SAndrew Thompson 					}
19863a3f90c6SAndrew Thompson 				} else {
1987e2524b2eSHans Petter Selasky 					if (!(sc->sc_mixer_clocks.bit_input[x / 8] &
1988e2524b2eSHans Petter Selasky 					    (1 << (x % 8)))) {
1989e2524b2eSHans Petter Selasky 						continue;
1990e2524b2eSHans Petter Selasky 					}
1991e2524b2eSHans Petter Selasky 				}
19923a3f90c6SAndrew Thompson 
1993e2524b2eSHans Petter Selasky 				DPRINTF("Checking clock ID=%d\n", x);
1994e2524b2eSHans Petter Selasky 
1995e2524b2eSHans Petter Selasky 				if (uaudio20_check_rate(udev,
1996e2524b2eSHans Petter Selasky 				    sc->sc_mixer_iface_no, x, rate)) {
1997e2524b2eSHans Petter Selasky 					DPRINTF("Unsupported sampling "
1998e2524b2eSHans Petter Selasky 					    "rate, id=%d\n", x);
1999e2524b2eSHans Petter Selasky 					goto next_ep;
2000e2524b2eSHans Petter Selasky 				}
2001e2524b2eSHans Petter Selasky 			}
2002e2524b2eSHans Petter Selasky 		} else {
2003e2524b2eSHans Petter Selasky 			uint16_t wFormat;
2004e2524b2eSHans Petter Selasky 
2005e2524b2eSHans Petter Selasky 			wFormat = UGETW(asid.v1->wFormatTag);
2006e2524b2eSHans Petter Selasky 			bChannels = UAUDIO_MAX_CHAN(asf1d.v1->bNrChannels);
2007f09566d3SHans Petter Selasky 			bBitResolution = asf1d.v1->bSubFrameSize * 8;
2008e2524b2eSHans Petter Selasky 
2009e2524b2eSHans Petter Selasky 			if (asf1d.v1->bSamFreqType == 0) {
2010e2524b2eSHans Petter Selasky 				DPRINTFN(16, "Sample rate: %d-%dHz\n",
2011e2524b2eSHans Petter Selasky 				    UA_SAMP_LO(asf1d.v1),
2012e2524b2eSHans Petter Selasky 				    UA_SAMP_HI(asf1d.v1));
2013e2524b2eSHans Petter Selasky 
2014e2524b2eSHans Petter Selasky 				if ((rate >= UA_SAMP_LO(asf1d.v1)) &&
2015e2524b2eSHans Petter Selasky 				    (rate <= UA_SAMP_HI(asf1d.v1)))
2016e2524b2eSHans Petter Selasky 					goto found_rate;
2017e2524b2eSHans Petter Selasky 			} else {
2018e2524b2eSHans Petter Selasky 				for (x = 0; x < asf1d.v1->bSamFreqType; x++) {
20193a3f90c6SAndrew Thompson 					DPRINTFN(16, "Sample rate = %dHz\n",
2020e2524b2eSHans Petter Selasky 					    UA_GETSAMP(asf1d.v1, x));
20213a3f90c6SAndrew Thompson 
2022e2524b2eSHans Petter Selasky 					if (rate == UA_GETSAMP(asf1d.v1, x))
20233a3f90c6SAndrew Thompson 						goto found_rate;
20243a3f90c6SAndrew Thompson 				}
20253a3f90c6SAndrew Thompson 			}
2026e2524b2eSHans Petter Selasky 			goto next_ep;
20273a3f90c6SAndrew Thompson 
20283a3f90c6SAndrew Thompson 	found_rate:
2029e2524b2eSHans Petter Selasky 			for (p_fmt = uaudio10_formats;
2030e2524b2eSHans Petter Selasky 			    p_fmt->wFormat != 0; p_fmt++) {
20313a3f90c6SAndrew Thompson 				if ((p_fmt->wFormat == wFormat) &&
2032e2524b2eSHans Petter Selasky 				    (p_fmt->bPrecision == bBitResolution))
2033e2524b2eSHans Petter Selasky 					break;
20343a3f90c6SAndrew Thompson 			}
2035e2524b2eSHans Petter Selasky 			if (p_fmt->wFormat == 0) {
2036e2524b2eSHans Petter Selasky 				DPRINTF("Unsupported audio format\n");
2037e2524b2eSHans Petter Selasky 				goto next_ep;
20383a3f90c6SAndrew Thompson 			}
20393a3f90c6SAndrew Thompson 
2040e2524b2eSHans Petter Selasky 			if ((bChannels != channels) ||
2041e2524b2eSHans Petter Selasky 			    (bBitResolution != bit_resolution)) {
2042e2524b2eSHans Petter Selasky 				DPRINTF("Wrong number of channels\n");
2043e2524b2eSHans Petter Selasky 				goto next_ep;
2044e2524b2eSHans Petter Selasky 			}
2045e2524b2eSHans Petter Selasky 		}
20463a3f90c6SAndrew Thompson 
204786c9b3f3SHans Petter Selasky 		chan = uaudio_get_chan(sc, (ep_dir == UE_DIR_OUT) ? &sc->sc_play_chan[0] :
204886c9b3f3SHans Petter Selasky 		    &sc->sc_rec_chan[0], curidx);
204986c9b3f3SHans Petter Selasky 		if (chan == NULL) {
205086c9b3f3SHans Petter Selasky 			DPRINTF("More than %d sub devices. (skipped)\n", UAUDIO_MAX_CHILD);
205186c9b3f3SHans Petter Selasky 			goto next_ep;
205286c9b3f3SHans Petter Selasky 		}
20533a3f90c6SAndrew Thompson 
2054455a367fSHans Petter Selasky 		if (usbd_get_iface(udev, curidx) == NULL) {
2055455a367fSHans Petter Selasky 			DPRINTF("Interface is not valid\n");
2056e2524b2eSHans Petter Selasky 			goto next_ep;
2057e2524b2eSHans Petter Selasky 		}
2058455a367fSHans Petter Selasky 		if (chan->num_alt == CHAN_MAX_ALT) {
2059455a367fSHans Petter Selasky 			DPRINTF("Too many alternate settings\n");
2060455a367fSHans Petter Selasky 			goto next_ep;
2061455a367fSHans Petter Selasky 		}
2062455a367fSHans Petter Selasky 		chan->set_alt = 0;
2063455a367fSHans Petter Selasky 		chan->cur_alt = CHAN_MAX_ALT;
20643a3f90c6SAndrew Thompson 
2065455a367fSHans Petter Selasky 		chan_alt = &chan->usb_alt[chan->num_alt++];
2066455a367fSHans Petter Selasky 
2067b850ecc1SAndrew Thompson #ifdef USB_DEBUG
20683a3f90c6SAndrew Thompson 		uaudio_chan_dump_ep_desc(ed1);
20693a3f90c6SAndrew Thompson #endif
20703a3f90c6SAndrew Thompson 		DPRINTF("Sample rate = %dHz, channels = %d, "
207186c9b3f3SHans Petter Selasky 		    "bits = %d, format = %s, ep 0x%02x, chan %p\n", rate, channels,
207286c9b3f3SHans Petter Selasky 		    bit_resolution, p_fmt->description, ed1->bEndpointAddress, chan);
20733a3f90c6SAndrew Thompson 
2074455a367fSHans Petter Selasky 		chan_alt->sample_rate = rate;
2075455a367fSHans Petter Selasky 		chan_alt->p_asf1d = asf1d;
2076455a367fSHans Petter Selasky 		chan_alt->p_ed1 = ed1;
2077455a367fSHans Petter Selasky 		chan_alt->p_fmt = p_fmt;
2078455a367fSHans Petter Selasky 		chan_alt->p_sed = sed;
2079455a367fSHans Petter Selasky 		chan_alt->iface_index = curidx;
2080455a367fSHans Petter Selasky 		chan_alt->iface_alt_index = alt_index;
2081455a367fSHans Petter Selasky 
20823a3f90c6SAndrew Thompson 		if (ep_dir == UE_DIR_IN)
2083455a367fSHans Petter Selasky 			chan_alt->usb_cfg = uaudio_cfg_record;
20843a3f90c6SAndrew Thompson 		else
2085455a367fSHans Petter Selasky 			chan_alt->usb_cfg = uaudio_cfg_play;
20863a3f90c6SAndrew Thompson 
2087455a367fSHans Petter Selasky 		chan_alt->sample_size = (UAUDIO_MAX_CHAN(channels) *
2088e2524b2eSHans Petter Selasky 		    p_fmt->bPrecision) / 8;
2089455a367fSHans Petter Selasky 		chan_alt->channels = channels;
20903a3f90c6SAndrew Thompson 
2091f895cc0cSHans Petter Selasky 		if (ep_dir == UE_DIR_IN &&
2092f895cc0cSHans Petter Selasky 		    usbd_get_speed(udev) == USB_SPEED_FULL) {
2093f895cc0cSHans Petter Selasky 			uaudio_record_fix_fs(ed1,
2094455a367fSHans Petter Selasky 			    chan_alt->sample_size * (rate / 1000),
2095455a367fSHans Petter Selasky 			    chan_alt->sample_size * (rate / 4000));
2096f895cc0cSHans Petter Selasky 		}
2097f895cc0cSHans Petter Selasky 
2098455a367fSHans Petter Selasky 		/* setup play/record format */
2099455a367fSHans Petter Selasky 
2100455a367fSHans Petter Selasky 		format = chan_alt->p_fmt->freebsd_fmt;
2101455a367fSHans Petter Selasky 
21029dd12733SHans Petter Selasky 		/* get default SND_FORMAT() */
21039dd12733SHans Petter Selasky 		format = SND_FORMAT(format, chan_alt->channels, 0);
21049dd12733SHans Petter Selasky 
2105455a367fSHans Petter Selasky 		switch (chan_alt->channels) {
21069dd12733SHans Petter Selasky 		uint32_t temp_fmt;
2107455a367fSHans Petter Selasky 		case 1:
21089dd12733SHans Petter Selasky 		case 2:
21099dd12733SHans Petter Selasky 			/* mono and stereo */
2110455a367fSHans Petter Selasky 			break;
2111455a367fSHans Petter Selasky 		default:
2112455a367fSHans Petter Selasky 			/* surround and more */
21139dd12733SHans Petter Selasky 			temp_fmt = feeder_matrix_default_format(format);
21149dd12733SHans Petter Selasky 			/* if multichannel, then format can be zero */
21159dd12733SHans Petter Selasky 			if (temp_fmt != 0)
21169dd12733SHans Petter Selasky 				format = temp_fmt;
2117455a367fSHans Petter Selasky 			break;
2118455a367fSHans Petter Selasky 		}
2119455a367fSHans Petter Selasky 
2120455a367fSHans Petter Selasky 		/* check if format is not supported */
2121455a367fSHans Petter Selasky 		if (format == 0) {
2122455a367fSHans Petter Selasky 			DPRINTF("The selected audio format is not supported\n");
2123455a367fSHans Petter Selasky 			chan->num_alt--;
2124455a367fSHans Petter Selasky 			goto next_ep;
2125455a367fSHans Petter Selasky 		}
2126ffae621eSHans Petter Selasky 		if (chan->num_alt > 1) {
2127455a367fSHans Petter Selasky 			/* we only accumulate one format at different sample rates */
2128ffae621eSHans Petter Selasky 			if (chan->pcm_format[0] != format) {
2129455a367fSHans Petter Selasky 				DPRINTF("Multiple formats is not supported\n");
2130455a367fSHans Petter Selasky 				chan->num_alt--;
2131455a367fSHans Petter Selasky 				goto next_ep;
2132455a367fSHans Petter Selasky 			}
2133ffae621eSHans Petter Selasky 			/* ignore if duplicate sample rate entry */
2134ffae621eSHans Petter Selasky 			if (rate == chan->usb_alt[chan->num_alt - 2].sample_rate) {
2135ffae621eSHans Petter Selasky 				DPRINTF("Duplicate sample rate detected\n");
2136ffae621eSHans Petter Selasky 				chan->num_alt--;
2137ffae621eSHans Petter Selasky 				goto next_ep;
2138ffae621eSHans Petter Selasky 			}
2139ffae621eSHans Petter Selasky 		}
2140455a367fSHans Petter Selasky 		chan->pcm_cap.fmtlist = chan->pcm_format;
2141455a367fSHans Petter Selasky 		chan->pcm_cap.fmtlist[0] = format;
2142455a367fSHans Petter Selasky 
21439dd12733SHans Petter Selasky 		/* check if device needs bitperfect */
21449dd12733SHans Petter Selasky 		if (chan_alt->channels > UAUDIO_MATRIX_MAX)
21459dd12733SHans Petter Selasky 			sc->sc_pcm_bitperfect = 1;
21469dd12733SHans Petter Selasky 
2147455a367fSHans Petter Selasky 		if (rate < chan->pcm_cap.minspeed || chan->pcm_cap.minspeed == 0)
2148455a367fSHans Petter Selasky 			chan->pcm_cap.minspeed = rate;
2149455a367fSHans Petter Selasky 		if (rate > chan->pcm_cap.maxspeed || chan->pcm_cap.maxspeed == 0)
2150455a367fSHans Petter Selasky 			chan->pcm_cap.maxspeed = rate;
2151455a367fSHans Petter Selasky 
2152e2524b2eSHans Petter Selasky 		if (sc->sc_sndstat_valid != 0) {
21533a3f90c6SAndrew Thompson 			sbuf_printf(&sc->sc_sndstat, "\n\t"
2154e2524b2eSHans Petter Selasky 			    "mode %d.%d:(%s) %dch, %dbit, %s, %dHz",
21553a3f90c6SAndrew Thompson 			    curidx, alt_index,
21563a3f90c6SAndrew Thompson 			    (ep_dir == UE_DIR_IN) ? "input" : "output",
2157e2524b2eSHans Petter Selasky 				    channels, p_fmt->bPrecision,
21583a3f90c6SAndrew Thompson 				    p_fmt->description, rate);
21593a3f90c6SAndrew Thompson 		}
2160e2524b2eSHans Petter Selasky 
2161e2524b2eSHans Petter Selasky 	next_ep:
2162e2524b2eSHans Petter Selasky 		sed.v1 = NULL;
2163e2524b2eSHans Petter Selasky 		ed1 = NULL;
21643a3f90c6SAndrew Thompson 	}
21653a3f90c6SAndrew Thompson }
21663a3f90c6SAndrew Thompson 
2167afbfddd9SAndrew Thompson /* This structure defines all the supported rates. */
2168afbfddd9SAndrew Thompson 
2169455a367fSHans Petter Selasky static const uint32_t uaudio_rate_list[CHAN_MAX_ALT] = {
21702c2752d3SHans Petter Selasky 	384000,
21712c2752d3SHans Petter Selasky 	352800,
21722c2752d3SHans Petter Selasky 	192000,
21732c2752d3SHans Petter Selasky 	176400,
2174afbfddd9SAndrew Thompson 	96000,
2175455a367fSHans Petter Selasky 	88200,
2176afbfddd9SAndrew Thompson 	88000,
2177afbfddd9SAndrew Thompson 	80000,
2178afbfddd9SAndrew Thompson 	72000,
2179afbfddd9SAndrew Thompson 	64000,
2180afbfddd9SAndrew Thompson 	56000,
2181afbfddd9SAndrew Thompson 	48000,
2182afbfddd9SAndrew Thompson 	44100,
2183afbfddd9SAndrew Thompson 	40000,
2184afbfddd9SAndrew Thompson 	32000,
2185afbfddd9SAndrew Thompson 	24000,
2186afbfddd9SAndrew Thompson 	22050,
2187afbfddd9SAndrew Thompson 	16000,
2188afbfddd9SAndrew Thompson 	11025,
2189afbfddd9SAndrew Thompson 	8000,
2190afbfddd9SAndrew Thompson 	0
2191afbfddd9SAndrew Thompson };
2192afbfddd9SAndrew Thompson 
21933a3f90c6SAndrew Thompson static void
uaudio_chan_fill_info(struct uaudio_softc * sc,struct usb_device * udev)2194760bc48eSAndrew Thompson uaudio_chan_fill_info(struct uaudio_softc *sc, struct usb_device *udev)
21953a3f90c6SAndrew Thompson {
21963a3f90c6SAndrew Thompson 	uint32_t rate = uaudio_default_rate;
2197afbfddd9SAndrew Thompson 	uint8_t z;
21983a3f90c6SAndrew Thompson 	uint8_t bits = uaudio_default_bits;
21993a3f90c6SAndrew Thompson 	uint8_t y;
22003a3f90c6SAndrew Thompson 	uint8_t channels = uaudio_default_channels;
220142fdcd9fSFlorian Walpen 	uint8_t channels_max;
22023a3f90c6SAndrew Thompson 	uint8_t x;
22033a3f90c6SAndrew Thompson 
22043a3f90c6SAndrew Thompson 	bits -= (bits % 8);
220542fdcd9fSFlorian Walpen 	if ((bits == 0) || (bits > UAUDIO_BITS_MAX)) {
22063a3f90c6SAndrew Thompson 		/* set a valid value */
220742fdcd9fSFlorian Walpen 		bits = UAUDIO_BITS_MAX;
22083a3f90c6SAndrew Thompson 	}
220942fdcd9fSFlorian Walpen 
221042fdcd9fSFlorian Walpen 	if (channels > UAUDIO_CHANNELS_MAX)
221142fdcd9fSFlorian Walpen 		channels = UAUDIO_CHANNELS_MAX;
2212afbfddd9SAndrew Thompson 	switch (usbd_get_speed(udev)) {
2213afbfddd9SAndrew Thompson 	case USB_SPEED_LOW:
2214afbfddd9SAndrew Thompson 	case USB_SPEED_FULL:
2215afbfddd9SAndrew Thompson 		/*
2216afbfddd9SAndrew Thompson 		 * Due to high bandwidth usage and problems
2217afbfddd9SAndrew Thompson 		 * with HIGH-speed split transactions we
2218afbfddd9SAndrew Thompson 		 * disable surround setups on FULL-speed USB
2219afbfddd9SAndrew Thompson 		 * by default
2220afbfddd9SAndrew Thompson 		 */
222142fdcd9fSFlorian Walpen 		channels_max = 4;
222242fdcd9fSFlorian Walpen 		/* more channels on request */
222342fdcd9fSFlorian Walpen 		if (channels > channels_max)
222442fdcd9fSFlorian Walpen 			channels_max = channels;
2225afbfddd9SAndrew Thompson 		break;
2226afbfddd9SAndrew Thompson 	default:
222742fdcd9fSFlorian Walpen 		channels_max = UAUDIO_CHANNELS_MAX;
2228afbfddd9SAndrew Thompson 		break;
2229afbfddd9SAndrew Thompson 	}
223042fdcd9fSFlorian Walpen 	if (channels == 0)
223142fdcd9fSFlorian Walpen 		channels = channels_max;
22329dd12733SHans Petter Selasky 
22339dd12733SHans Petter Selasky 	if (sbuf_new(&sc->sc_sndstat, NULL, 4096, SBUF_AUTOEXTEND))
22343a3f90c6SAndrew Thompson 		sc->sc_sndstat_valid = 1;
22359dd12733SHans Petter Selasky 
22363a3f90c6SAndrew Thompson 	/* try to search for a valid config */
22373a3f90c6SAndrew Thompson 
22383a3f90c6SAndrew Thompson 	for (x = channels; x; x--) {
22393a3f90c6SAndrew Thompson 		for (y = bits; y; y -= 8) {
2240afbfddd9SAndrew Thompson 			/* try user defined rate, if any */
2241afbfddd9SAndrew Thompson 			if (rate != 0)
2242afbfddd9SAndrew Thompson 				uaudio_chan_fill_info_sub(sc, udev, rate, x, y);
2243afbfddd9SAndrew Thompson 
2244afbfddd9SAndrew Thompson 			/* try find a matching rate, if any */
224542fdcd9fSFlorian Walpen 			for (z = 0; uaudio_rate_list[z]; z++) {
224642fdcd9fSFlorian Walpen 				if (uaudio_rate_list[z] != rate)
224742fdcd9fSFlorian Walpen 					uaudio_chan_fill_info_sub(sc, udev,
224842fdcd9fSFlorian Walpen 					    uaudio_rate_list[z], x, y);
22493a3f90c6SAndrew Thompson 			}
225042fdcd9fSFlorian Walpen 
225142fdcd9fSFlorian Walpen 			/* after default value in first round, proceed with max bits */
225242fdcd9fSFlorian Walpen 			if (y == bits)
225342fdcd9fSFlorian Walpen 				y = UAUDIO_BITS_MAX + 8;
225442fdcd9fSFlorian Walpen 			/* skip default value subsequently */
225542fdcd9fSFlorian Walpen 			if (y == (bits + 8))
225642fdcd9fSFlorian Walpen 				y -= 8;
225742fdcd9fSFlorian Walpen 		}
225842fdcd9fSFlorian Walpen 
225942fdcd9fSFlorian Walpen 		/* after default value in first round, proceed with max channels */
226042fdcd9fSFlorian Walpen 		if (x == channels)
226142fdcd9fSFlorian Walpen 			x = channels_max + 1;
226242fdcd9fSFlorian Walpen 		/* skip default value subsequently */
226342fdcd9fSFlorian Walpen 		if (x == (channels + 1))
226442fdcd9fSFlorian Walpen 			x--;
22653a3f90c6SAndrew Thompson 	}
2266455a367fSHans Petter Selasky 	if (sc->sc_sndstat_valid)
22673a3f90c6SAndrew Thompson 		sbuf_finish(&sc->sc_sndstat);
22683a3f90c6SAndrew Thompson }
22693a3f90c6SAndrew Thompson 
22703a3f90c6SAndrew Thompson static void
uaudio_chan_play_sync_callback(struct usb_xfer * xfer,usb_error_t error)2271b4380da7SHans Petter Selasky uaudio_chan_play_sync_callback(struct usb_xfer *xfer, usb_error_t error)
2272b4380da7SHans Petter Selasky {
2273b4380da7SHans Petter Selasky 	struct uaudio_chan *ch = usbd_xfer_softc(xfer);
2274b4380da7SHans Petter Selasky 	struct usb_page_cache *pc;
227585bad582SHans Petter Selasky 	uint64_t sample_rate;
2276b4380da7SHans Petter Selasky 	uint8_t buf[4];
2277b4380da7SHans Petter Selasky 	uint64_t temp;
227886c9b3f3SHans Petter Selasky 	unsigned i;
2279b4380da7SHans Petter Selasky 	int len;
2280b4380da7SHans Petter Selasky 	int actlen;
2281b4380da7SHans Petter Selasky 	int nframes;
2282b4380da7SHans Petter Selasky 
2283b4380da7SHans Petter Selasky 	usbd_xfer_status(xfer, &actlen, NULL, NULL, &nframes);
2284b4380da7SHans Petter Selasky 
228586c9b3f3SHans Petter Selasky 	i = uaudio_get_child_index_by_chan(ch->priv_sc, ch);
228686c9b3f3SHans Petter Selasky 
2287b4380da7SHans Petter Selasky 	switch (USB_GET_STATE(xfer)) {
2288b4380da7SHans Petter Selasky 	case USB_ST_TRANSFERRED:
2289b4380da7SHans Petter Selasky 
2290b4380da7SHans Petter Selasky 		DPRINTFN(6, "transferred %d bytes\n", actlen);
2291b4380da7SHans Petter Selasky 
2292b4380da7SHans Petter Selasky 		if (nframes == 0)
2293b4380da7SHans Petter Selasky 			break;
2294b4380da7SHans Petter Selasky 		len = usbd_xfer_frame_len(xfer, 0);
2295b4380da7SHans Petter Selasky 		if (len == 0)
2296b4380da7SHans Petter Selasky 			break;
2297b4380da7SHans Petter Selasky 		if (len > sizeof(buf))
2298b4380da7SHans Petter Selasky 			len = sizeof(buf);
2299b4380da7SHans Petter Selasky 
2300b4380da7SHans Petter Selasky 		memset(buf, 0, sizeof(buf));
2301b4380da7SHans Petter Selasky 
2302b4380da7SHans Petter Selasky 		pc = usbd_xfer_get_frame(xfer, 0);
2303b4380da7SHans Petter Selasky 		usbd_copy_out(pc, 0, buf, len);
2304b4380da7SHans Petter Selasky 
2305b4380da7SHans Petter Selasky 		temp = UGETDW(buf);
2306b4380da7SHans Petter Selasky 
2307b4380da7SHans Petter Selasky 		DPRINTF("Value = 0x%08x\n", (int)temp);
2308b4380da7SHans Petter Selasky 
2309b4380da7SHans Petter Selasky 		/* auto-detect SYNC format */
2310b4380da7SHans Petter Selasky 
2311b4380da7SHans Petter Selasky 		if (len == 4)
2312b4380da7SHans Petter Selasky 			temp &= 0x0fffffff;
2313b4380da7SHans Petter Selasky 
2314b4380da7SHans Petter Selasky 		/* check for no data */
2315b4380da7SHans Petter Selasky 
2316b4380da7SHans Petter Selasky 		if (temp == 0)
2317b4380da7SHans Petter Selasky 			break;
2318b4380da7SHans Petter Selasky 
2319a931ce67SHans Petter Selasky 		temp *= 125ULL;
2320b4380da7SHans Petter Selasky 
232185bad582SHans Petter Selasky 		sample_rate = ch->usb_alt[ch->cur_alt].sample_rate;
232285bad582SHans Petter Selasky 
2323b4380da7SHans Petter Selasky 		/* auto adjust */
2324455a367fSHans Petter Selasky 		while (temp < (sample_rate - (sample_rate / 4)))
2325b4380da7SHans Petter Selasky 			temp *= 2;
2326b4380da7SHans Petter Selasky 
2327455a367fSHans Petter Selasky 		while (temp > (sample_rate + (sample_rate / 2)))
2328b4380da7SHans Petter Selasky 			temp /= 2;
2329b4380da7SHans Petter Selasky 
233085bad582SHans Petter Selasky 		DPRINTF("Comparing %d Hz :: %d Hz\n",
233185bad582SHans Petter Selasky 		    (int)temp, (int)sample_rate);
2332b4380da7SHans Petter Selasky 
233387087b85SHans Petter Selasky 		/*
233487087b85SHans Petter Selasky 		 * Use feedback value as fallback when there is no
233587087b85SHans Petter Selasky 		 * recording channel:
233687087b85SHans Petter Selasky 		 */
233786c9b3f3SHans Petter Selasky 		if (ch->priv_sc->sc_rec_chan[i].num_alt == 0) {
233882364f24SHans Petter Selasky 			int32_t jitter_max = howmany(sample_rate, 16000);
233982364f24SHans Petter Selasky 
2340dd3dde98SHans Petter Selasky 			/*
2341dd3dde98SHans Petter Selasky 			 * Range check the jitter values to avoid
2342dd3dde98SHans Petter Selasky 			 * bogus sample rate adjustments. The expected
2343dd3dde98SHans Petter Selasky 			 * deviation should not be more than 1Hz per
2344dd3dde98SHans Petter Selasky 			 * second. The USB v2.0 specification also
2345dd3dde98SHans Petter Selasky 			 * mandates this requirement. Refer to chapter
2346dd3dde98SHans Petter Selasky 			 * 5.12.4.2 about feedback.
2347dd3dde98SHans Petter Selasky 			 */
234882364f24SHans Petter Selasky 			ch->jitter_curr = temp - sample_rate;
234982364f24SHans Petter Selasky 			if (ch->jitter_curr > jitter_max)
235082364f24SHans Petter Selasky 				ch->jitter_curr = jitter_max;
235182364f24SHans Petter Selasky 			else if (ch->jitter_curr < -jitter_max)
235282364f24SHans Petter Selasky 				ch->jitter_curr = -jitter_max;
2353dd3dde98SHans Petter Selasky 		}
235485bad582SHans Petter Selasky 		ch->feedback_rate = temp;
2355b4380da7SHans Petter Selasky 		break;
2356b4380da7SHans Petter Selasky 
2357b4380da7SHans Petter Selasky 	case USB_ST_SETUP:
2358fb8a716dSHans Petter Selasky 		/*
2359fb8a716dSHans Petter Selasky 		 * Check if the recording stream can be used as a
2360fb8a716dSHans Petter Selasky 		 * source of jitter information to save some
2361fb8a716dSHans Petter Selasky 		 * isochronous bandwidth:
2362fb8a716dSHans Petter Selasky 		 */
236386c9b3f3SHans Petter Selasky 		if (ch->priv_sc->sc_rec_chan[i].num_alt != 0 &&
2364fb8a716dSHans Petter Selasky 		    uaudio_debug == 0)
2365fb8a716dSHans Petter Selasky 			break;
2366b4380da7SHans Petter Selasky 		usbd_xfer_set_frames(xfer, 1);
2367b4380da7SHans Petter Selasky 		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_framelen(xfer));
2368b4380da7SHans Petter Selasky 		usbd_transfer_submit(xfer);
2369b4380da7SHans Petter Selasky 		break;
2370b4380da7SHans Petter Selasky 
2371b4380da7SHans Petter Selasky 	default:			/* Error */
2372b4380da7SHans Petter Selasky 		break;
2373b4380da7SHans Petter Selasky 	}
2374b4380da7SHans Petter Selasky }
2375b4380da7SHans Petter Selasky 
237685bad582SHans Petter Selasky static int
uaudio_chan_is_async(struct uaudio_chan * ch,uint8_t alt)237785bad582SHans Petter Selasky uaudio_chan_is_async(struct uaudio_chan *ch, uint8_t alt)
237885bad582SHans Petter Selasky {
237985bad582SHans Petter Selasky 	uint8_t attr = ch->usb_alt[alt].p_ed1->bmAttributes;
238085bad582SHans Petter Selasky 	return (UE_GET_ISO_TYPE(attr) == UE_ISO_ASYNC);
238185bad582SHans Petter Selasky }
238285bad582SHans Petter Selasky 
2383b4380da7SHans Petter Selasky static void
uaudio_chan_play_callback(struct usb_xfer * xfer,usb_error_t error)2384ed6d949aSAndrew Thompson uaudio_chan_play_callback(struct usb_xfer *xfer, usb_error_t error)
23853a3f90c6SAndrew Thompson {
2386ed6d949aSAndrew Thompson 	struct uaudio_chan *ch = usbd_xfer_softc(xfer);
238785bad582SHans Petter Selasky 	struct uaudio_chan *ch_rec;
2388ed6d949aSAndrew Thompson 	struct usb_page_cache *pc;
2389b4380da7SHans Petter Selasky 	uint32_t mfl;
23903a3f90c6SAndrew Thompson 	uint32_t total;
23913a3f90c6SAndrew Thompson 	uint32_t blockcount;
23923a3f90c6SAndrew Thompson 	uint32_t n;
23933a3f90c6SAndrew Thompson 	uint32_t offset;
239486c9b3f3SHans Petter Selasky 	unsigned i;
239585bad582SHans Petter Selasky 	int sample_size;
2396afbfddd9SAndrew Thompson 	int actlen;
2397afbfddd9SAndrew Thompson 	int sumlen;
2398ed6d949aSAndrew Thompson 
239985bad582SHans Petter Selasky 	if (ch->running == 0 || ch->start == ch->end) {
240085bad582SHans Petter Selasky 		DPRINTF("not running or no buffer!\n");
2401b029f6bbSAndrew Thompson 		return;
2402b029f6bbSAndrew Thompson 	}
24033a3f90c6SAndrew Thompson 
240486c9b3f3SHans Petter Selasky 	i = uaudio_get_child_index_by_chan(ch->priv_sc, ch);
240586c9b3f3SHans Petter Selasky 
240686c9b3f3SHans Petter Selasky 	/* check if there is a valid record channel */
240786c9b3f3SHans Petter Selasky 	ch_rec = ch->priv_sc->sc_rec_chan + i;
240886c9b3f3SHans Petter Selasky 
240986c9b3f3SHans Petter Selasky 	if (ch_rec->num_alt == 0)
241085bad582SHans Petter Selasky 		ch_rec = NULL;
241185bad582SHans Petter Selasky 
241285bad582SHans Petter Selasky 	usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
241385bad582SHans Petter Selasky 
24143a3f90c6SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
241585bad582SHans Petter Selasky 	case USB_ST_SETUP:
241685bad582SHans Petter Selasky tr_setup:
241785bad582SHans Petter Selasky 		if (ch_rec != NULL) {
241812148d43SHans Petter Selasky 			/*
241912148d43SHans Petter Selasky 			 * NOTE: The play and record callbacks are
242012148d43SHans Petter Selasky 			 * executed from the same USB thread and
242112148d43SHans Petter Selasky 			 * locking the record channel mutex here is
242212148d43SHans Petter Selasky 			 * not needed. This avoids a LOR situation.
242312148d43SHans Petter Selasky 			 */
242412148d43SHans Petter Selasky 
242585bad582SHans Petter Selasky 			/* reset receive jitter counters */
242685bad582SHans Petter Selasky 			ch_rec->jitter_curr = 0;
242785bad582SHans Petter Selasky 			ch_rec->jitter_rem = 0;
242885bad582SHans Petter Selasky 		}
242985bad582SHans Petter Selasky 
243085bad582SHans Petter Selasky 		/* reset transmit jitter counters */
243185bad582SHans Petter Selasky 		ch->jitter_curr = 0;
243285bad582SHans Petter Selasky 		ch->jitter_rem = 0;
243385bad582SHans Petter Selasky 
243485bad582SHans Petter Selasky 		/* FALLTHROUGH */
24353a3f90c6SAndrew Thompson 	case USB_ST_TRANSFERRED:
2436ed6d949aSAndrew Thompson 		if (actlen < sumlen) {
24373a3f90c6SAndrew Thompson 			DPRINTF("short transfer, "
2438afbfddd9SAndrew Thompson 			    "%d of %d bytes\n", actlen, sumlen);
24393a3f90c6SAndrew Thompson 		}
24403a3f90c6SAndrew Thompson 		chn_intr(ch->pcm_ch);
24413a3f90c6SAndrew Thompson 
244285bad582SHans Petter Selasky 		/*
244385bad582SHans Petter Selasky 		 * Check for asynchronous playback endpoint and that
244485bad582SHans Petter Selasky 		 * the playback endpoint is properly configured:
244585bad582SHans Petter Selasky 		 */
244685bad582SHans Petter Selasky 		if (ch_rec != NULL &&
244785bad582SHans Petter Selasky 		    uaudio_chan_is_async(ch, ch->cur_alt) != 0) {
244812148d43SHans Petter Selasky 			uint32_t rec_alt = ch_rec->cur_alt;
244912148d43SHans Petter Selasky 			if (rec_alt < ch_rec->num_alt) {
245085bad582SHans Petter Selasky 				int64_t tx_jitter;
245185bad582SHans Petter Selasky 				int64_t rx_rate;
245212148d43SHans Petter Selasky 				/*
245312148d43SHans Petter Selasky 				 * NOTE: The play and record callbacks
245412148d43SHans Petter Selasky 				 * are executed from the same USB
245512148d43SHans Petter Selasky 				 * thread and locking the record
245612148d43SHans Petter Selasky 				 * channel mutex here is not needed.
245712148d43SHans Petter Selasky 				 * This avoids a LOR situation.
245812148d43SHans Petter Selasky 				 */
245985bad582SHans Petter Selasky 
246085bad582SHans Petter Selasky 				/* translate receive jitter into transmit jitter */
246185bad582SHans Petter Selasky 				tx_jitter = ch->usb_alt[ch->cur_alt].sample_rate;
246285bad582SHans Petter Selasky 				tx_jitter = (tx_jitter * ch_rec->jitter_curr) +
246385bad582SHans Petter Selasky 				    ch->jitter_rem;
246485bad582SHans Petter Selasky 
246585bad582SHans Petter Selasky 				/* reset receive jitter counters */
246685bad582SHans Petter Selasky 				ch_rec->jitter_curr = 0;
246785bad582SHans Petter Selasky 				ch_rec->jitter_rem = 0;
246885bad582SHans Petter Selasky 
246985bad582SHans Petter Selasky 				/* compute exact number of transmit jitter samples */
247012148d43SHans Petter Selasky 				rx_rate = ch_rec->usb_alt[rec_alt].sample_rate;
247185bad582SHans Petter Selasky 				ch->jitter_curr += tx_jitter / rx_rate;
247285bad582SHans Petter Selasky 				ch->jitter_rem = tx_jitter % rx_rate;
247385bad582SHans Petter Selasky 			}
247485bad582SHans Petter Selasky 		}
247585bad582SHans Petter Selasky 
2476a931ce67SHans Petter Selasky 		/* start the SYNC transfer one time per second, if any */
24774d76235fSHans Petter Selasky 		ch->intr_counter += ch->intr_frames;
24784d76235fSHans Petter Selasky 		if (ch->intr_counter >= ch->frames_per_second) {
24794d76235fSHans Petter Selasky 			ch->intr_counter -= ch->frames_per_second;
2480b4380da7SHans Petter Selasky 			usbd_transfer_start(ch->xfer[UAUDIO_NCHANBUFS]);
248126ec3491SHans Petter Selasky 		}
2482b4380da7SHans Petter Selasky 
2483b4380da7SHans Petter Selasky 		mfl = usbd_xfer_max_framelen(xfer);
2484b4380da7SHans Petter Selasky 
2485b4380da7SHans Petter Selasky 		if (ch->bytes_per_frame[1] > mfl) {
24863a3f90c6SAndrew Thompson 			DPRINTF("bytes per transfer, %d, "
24873a3f90c6SAndrew Thompson 			    "exceeds maximum, %d!\n",
2488afbfddd9SAndrew Thompson 			    ch->bytes_per_frame[1],
2489b4380da7SHans Petter Selasky 			    mfl);
24903a3f90c6SAndrew Thompson 			break;
24913a3f90c6SAndrew Thompson 		}
2492afbfddd9SAndrew Thompson 
2493afbfddd9SAndrew Thompson 		blockcount = ch->intr_frames;
2494afbfddd9SAndrew Thompson 
2495afbfddd9SAndrew Thompson 		/* setup number of frames */
2496ed6d949aSAndrew Thompson 		usbd_xfer_set_frames(xfer, blockcount);
2497afbfddd9SAndrew Thompson 
249885bad582SHans Petter Selasky 		/* get sample size */
249985bad582SHans Petter Selasky 		sample_size = ch->usb_alt[ch->cur_alt].sample_size;
250085bad582SHans Petter Selasky 
2501afbfddd9SAndrew Thompson 		/* reset total length */
2502afbfddd9SAndrew Thompson 		total = 0;
2503afbfddd9SAndrew Thompson 
2504afbfddd9SAndrew Thompson 		/* setup frame lengths */
2505afbfddd9SAndrew Thompson 		for (n = 0; n != blockcount; n++) {
2506b4380da7SHans Petter Selasky 			uint32_t frame_len;
2507b4380da7SHans Petter Selasky 
2508afbfddd9SAndrew Thompson 			ch->sample_curr += ch->sample_rem;
2509afbfddd9SAndrew Thompson 			if (ch->sample_curr >= ch->frames_per_second) {
2510afbfddd9SAndrew Thompson 				ch->sample_curr -= ch->frames_per_second;
2511b4380da7SHans Petter Selasky 				frame_len = ch->bytes_per_frame[1];
2512afbfddd9SAndrew Thompson 			} else {
2513b4380da7SHans Petter Selasky 				frame_len = ch->bytes_per_frame[0];
2514afbfddd9SAndrew Thompson 			}
2515b4380da7SHans Petter Selasky 
251685bad582SHans Petter Selasky 			/* handle free running clock case */
251785bad582SHans Petter Selasky 			if (ch->jitter_curr > 0 &&
251885bad582SHans Petter Selasky 			    (frame_len + sample_size) <= mfl) {
2519b4380da7SHans Petter Selasky 				DPRINTFN(6, "sending one sample more\n");
252085bad582SHans Petter Selasky 				ch->jitter_curr--;
2521455a367fSHans Petter Selasky 				frame_len += sample_size;
252285bad582SHans Petter Selasky 			} else if (ch->jitter_curr < 0 &&
252385bad582SHans Petter Selasky 			    frame_len >= sample_size) {
2524b4380da7SHans Petter Selasky 				DPRINTFN(6, "sending one sample less\n");
252585bad582SHans Petter Selasky 				ch->jitter_curr++;
2526455a367fSHans Petter Selasky 				frame_len -= sample_size;
2527b4380da7SHans Petter Selasky 			}
2528b4380da7SHans Petter Selasky 			usbd_xfer_set_frame_len(xfer, n, frame_len);
2529b4380da7SHans Petter Selasky 			total += frame_len;
2530afbfddd9SAndrew Thompson 		}
25313a3f90c6SAndrew Thompson 
253285bad582SHans Petter Selasky 		DPRINTFN(6, "transferring %d bytes\n", total);
25333a3f90c6SAndrew Thompson 
25343a3f90c6SAndrew Thompson 		offset = 0;
25353a3f90c6SAndrew Thompson 
2536ed6d949aSAndrew Thompson 		pc = usbd_xfer_get_frame(xfer, 0);
25373a3f90c6SAndrew Thompson 		while (total > 0) {
25383a3f90c6SAndrew Thompson 			n = (ch->end - ch->cur);
253985bad582SHans Petter Selasky 			if (n > total)
25403a3f90c6SAndrew Thompson 				n = total;
254185bad582SHans Petter Selasky 
2542ed6d949aSAndrew Thompson 			usbd_copy_in(pc, offset, ch->cur, n);
25433a3f90c6SAndrew Thompson 
25443a3f90c6SAndrew Thompson 			total -= n;
25453a3f90c6SAndrew Thompson 			ch->cur += n;
25463a3f90c6SAndrew Thompson 			offset += n;
25473a3f90c6SAndrew Thompson 
254885bad582SHans Petter Selasky 			if (ch->cur >= ch->end)
25493a3f90c6SAndrew Thompson 				ch->cur = ch->start;
25503a3f90c6SAndrew Thompson 		}
2551a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
25523a3f90c6SAndrew Thompson 		break;
25533a3f90c6SAndrew Thompson 
25543a3f90c6SAndrew Thompson 	default:			/* Error */
255585bad582SHans Petter Selasky 		if (error != USB_ERR_CANCELLED)
255685bad582SHans Petter Selasky 			goto tr_setup;
25573a3f90c6SAndrew Thompson 		break;
25583a3f90c6SAndrew Thompson 	}
25593a3f90c6SAndrew Thompson }
25603a3f90c6SAndrew Thompson 
25613a3f90c6SAndrew Thompson static void
uaudio_chan_record_sync_callback(struct usb_xfer * xfer,usb_error_t error)2562b4380da7SHans Petter Selasky uaudio_chan_record_sync_callback(struct usb_xfer *xfer, usb_error_t error)
2563b4380da7SHans Petter Selasky {
2564b4380da7SHans Petter Selasky 	/* TODO */
2565b4380da7SHans Petter Selasky }
2566b4380da7SHans Petter Selasky 
2567b4380da7SHans Petter Selasky static void
uaudio_chan_record_callback(struct usb_xfer * xfer,usb_error_t error)2568ed6d949aSAndrew Thompson uaudio_chan_record_callback(struct usb_xfer *xfer, usb_error_t error)
25693a3f90c6SAndrew Thompson {
2570ed6d949aSAndrew Thompson 	struct uaudio_chan *ch = usbd_xfer_softc(xfer);
2571ed6d949aSAndrew Thompson 	struct usb_page_cache *pc;
25723a3f90c6SAndrew Thompson 	uint32_t offset0;
2573b029f6bbSAndrew Thompson 	uint32_t mfl;
25746d917491SHans Petter Selasky 	int m;
25756d917491SHans Petter Selasky 	int n;
2576ed6d949aSAndrew Thompson 	int len;
2577b029f6bbSAndrew Thompson 	int actlen;
2578b029f6bbSAndrew Thompson 	int nframes;
257985bad582SHans Petter Selasky 	int expected_bytes;
258085bad582SHans Petter Selasky 	int sample_size;
2581ed6d949aSAndrew Thompson 
258285bad582SHans Petter Selasky 	if (ch->start == ch->end) {
2583b029f6bbSAndrew Thompson 		DPRINTF("no buffer!\n");
2584b029f6bbSAndrew Thompson 		return;
2585b029f6bbSAndrew Thompson 	}
25863a3f90c6SAndrew Thompson 
258785bad582SHans Petter Selasky 	usbd_xfer_status(xfer, &actlen, NULL, NULL, &nframes);
258885bad582SHans Petter Selasky 	mfl = usbd_xfer_max_framelen(xfer);
258985bad582SHans Petter Selasky 
25903a3f90c6SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
25913a3f90c6SAndrew Thompson 	case USB_ST_TRANSFERRED:
2592afbfddd9SAndrew Thompson 
25933a3f90c6SAndrew Thompson 		offset0 = 0;
2594b029f6bbSAndrew Thompson 		pc = usbd_xfer_get_frame(xfer, 0);
25953a3f90c6SAndrew Thompson 
259685bad582SHans Petter Selasky 		/* try to compute the number of expected bytes */
259785bad582SHans Petter Selasky 		ch->sample_curr += (ch->sample_rem * ch->intr_frames);
25983a3f90c6SAndrew Thompson 
259985bad582SHans Petter Selasky 		/* compute number of expected bytes */
260085bad582SHans Petter Selasky 		expected_bytes = (ch->intr_frames * ch->bytes_per_frame[0]) +
260185bad582SHans Petter Selasky 		    ((ch->sample_curr / ch->frames_per_second) *
260285bad582SHans Petter Selasky 		    (ch->bytes_per_frame[1] - ch->bytes_per_frame[0]));
260385bad582SHans Petter Selasky 
260485bad582SHans Petter Selasky 		/* keep remainder */
260585bad582SHans Petter Selasky 		ch->sample_curr %= ch->frames_per_second;
260685bad582SHans Petter Selasky 
260785bad582SHans Petter Selasky 		/* get current sample size */
260885bad582SHans Petter Selasky 		sample_size = ch->usb_alt[ch->cur_alt].sample_size;
260985bad582SHans Petter Selasky 
261085bad582SHans Petter Selasky 		for (n = 0; n != nframes; n++) {
261185bad582SHans Petter Selasky 			uint32_t offset1 = offset0;
261285bad582SHans Petter Selasky 
26138f9e0ef9SAndrew Thompson 			len = usbd_xfer_frame_len(xfer, n);
26143a3f90c6SAndrew Thompson 
261585bad582SHans Petter Selasky 			/* make sure we only receive complete samples */
261685bad582SHans Petter Selasky 			len = len - (len % sample_size);
261785bad582SHans Petter Selasky 
261885bad582SHans Petter Selasky 			/* subtract bytes received from expected payload */
261985bad582SHans Petter Selasky 			expected_bytes -= len;
262085bad582SHans Petter Selasky 
262185bad582SHans Petter Selasky 			/* don't receive data when not ready */
262285bad582SHans Petter Selasky 			if (ch->running == 0 || ch->cur_alt != ch->set_alt)
262385bad582SHans Petter Selasky 				continue;
262485bad582SHans Petter Selasky 
262585bad582SHans Petter Selasky 			/* fill ring buffer with samples, if any */
2626ed6d949aSAndrew Thompson 			while (len > 0) {
26273a3f90c6SAndrew Thompson 				m = (ch->end - ch->cur);
26283a3f90c6SAndrew Thompson 
26296d917491SHans Petter Selasky 				if (m > len)
2630ed6d949aSAndrew Thompson 					m = len;
26316d917491SHans Petter Selasky 
2632ed6d949aSAndrew Thompson 				usbd_copy_out(pc, offset1, ch->cur, m);
26333a3f90c6SAndrew Thompson 
2634ed6d949aSAndrew Thompson 				len -= m;
26353a3f90c6SAndrew Thompson 				offset1 += m;
26363a3f90c6SAndrew Thompson 				ch->cur += m;
26373a3f90c6SAndrew Thompson 
263885bad582SHans Petter Selasky 				if (ch->cur >= ch->end)
26393a3f90c6SAndrew Thompson 					ch->cur = ch->start;
26403a3f90c6SAndrew Thompson 			}
26413a3f90c6SAndrew Thompson 
2642b029f6bbSAndrew Thompson 			offset0 += mfl;
26433a3f90c6SAndrew Thompson 		}
26443a3f90c6SAndrew Thompson 
264585bad582SHans Petter Selasky 		/* update current jitter */
264685bad582SHans Petter Selasky 		ch->jitter_curr -= (expected_bytes / sample_size);
264785bad582SHans Petter Selasky 
264885bad582SHans Petter Selasky 		/* don't allow a huge amount of jitter to accumulate */
264985bad582SHans Petter Selasky 		nframes = 2 * ch->intr_frames;
265085bad582SHans Petter Selasky 
265185bad582SHans Petter Selasky 		/* range check current jitter */
265285bad582SHans Petter Selasky 		if (ch->jitter_curr < -nframes)
265385bad582SHans Petter Selasky 			ch->jitter_curr = -nframes;
265485bad582SHans Petter Selasky 		else if (ch->jitter_curr > nframes)
265585bad582SHans Petter Selasky 			ch->jitter_curr = nframes;
265685bad582SHans Petter Selasky 
265785bad582SHans Petter Selasky 		DPRINTFN(6, "transferred %d bytes, jitter %d samples\n",
265885bad582SHans Petter Selasky 		    actlen, ch->jitter_curr);
265985bad582SHans Petter Selasky 
266085bad582SHans Petter Selasky 		if (ch->running != 0)
26613a3f90c6SAndrew Thompson 			chn_intr(ch->pcm_ch);
26623a3f90c6SAndrew Thompson 
26633a3f90c6SAndrew Thompson 	case USB_ST_SETUP:
2664b029f6bbSAndrew Thompson tr_setup:
266585bad582SHans Petter Selasky 		nframes = ch->intr_frames;
2666afbfddd9SAndrew Thompson 
266785bad582SHans Petter Selasky 		usbd_xfer_set_frames(xfer, nframes);
266885bad582SHans Petter Selasky 		for (n = 0; n != nframes; n++)
2669b029f6bbSAndrew Thompson 			usbd_xfer_set_frame_len(xfer, n, mfl);
26703a3f90c6SAndrew Thompson 
2671a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
2672b029f6bbSAndrew Thompson 		break;
26733a3f90c6SAndrew Thompson 
26743a3f90c6SAndrew Thompson 	default:			/* Error */
267585bad582SHans Petter Selasky 		if (error != USB_ERR_CANCELLED)
2676b029f6bbSAndrew Thompson 			goto tr_setup;
267785bad582SHans Petter Selasky 		break;
26783a3f90c6SAndrew Thompson 	}
26793a3f90c6SAndrew Thompson }
26803a3f90c6SAndrew Thompson 
26813a3f90c6SAndrew Thompson void   *
uaudio_chan_init(struct uaudio_chan * ch,struct snd_dbuf * b,struct pcm_channel * c,int dir)268286c9b3f3SHans Petter Selasky uaudio_chan_init(struct uaudio_chan *ch, struct snd_dbuf *b,
26833a3f90c6SAndrew Thompson     struct pcm_channel *c, int dir)
26843a3f90c6SAndrew Thompson {
26853a3f90c6SAndrew Thompson 	uint32_t buf_size;
2686455a367fSHans Petter Selasky 	uint8_t x;
26873a3f90c6SAndrew Thompson 
2688455a367fSHans Petter Selasky 	/* store mutex and PCM channel */
26897fb43570SAndrew Thompson 
26907fb43570SAndrew Thompson 	ch->pcm_ch = c;
26917fb43570SAndrew Thompson 	ch->pcm_mtx = c->lock;
26927fb43570SAndrew Thompson 
2693455a367fSHans Petter Selasky 	/* compute worst case buffer */
26943a3f90c6SAndrew Thompson 
2695455a367fSHans Petter Selasky 	buf_size = 0;
2696455a367fSHans Petter Selasky 	for (x = 0; x != ch->num_alt; x++) {
2697b2e97edfSFlorian Walpen 		uint32_t temp = uaudio_max_buffer_size(ch, x);
2698455a367fSHans Petter Selasky 		if (temp > buf_size)
2699455a367fSHans Petter Selasky 			buf_size = temp;
2700afbfddd9SAndrew Thompson 	}
2701afbfddd9SAndrew Thompson 
2702455a367fSHans Petter Selasky 	/* allow double buffering */
2703b029f6bbSAndrew Thompson 	buf_size *= 2;
2704455a367fSHans Petter Selasky 
2705455a367fSHans Petter Selasky 	DPRINTF("Worst case buffer is %d bytes\n", (int)buf_size);
2706b029f6bbSAndrew Thompson 
2707b029f6bbSAndrew Thompson 	ch->buf = malloc(buf_size, M_DEVBUF, M_WAITOK | M_ZERO);
2708b029f6bbSAndrew Thompson 	if (ch->buf == NULL)
2709b029f6bbSAndrew Thompson 		goto error;
2710b029f6bbSAndrew Thompson 	if (sndbuf_setup(b, ch->buf, buf_size) != 0)
2711b029f6bbSAndrew Thompson 		goto error;
2712b029f6bbSAndrew Thompson 
2713b029f6bbSAndrew Thompson 	ch->start = ch->buf;
2714b029f6bbSAndrew Thompson 	ch->end = ch->buf + buf_size;
2715b029f6bbSAndrew Thompson 	ch->cur = ch->buf;
2716b029f6bbSAndrew Thompson 	ch->pcm_buf = b;
2717455a367fSHans Petter Selasky 	ch->max_buf = buf_size;
2718b029f6bbSAndrew Thompson 
2719b029f6bbSAndrew Thompson 	if (ch->pcm_mtx == NULL) {
2720b029f6bbSAndrew Thompson 		DPRINTF("ERROR: PCM channels does not have a mutex!\n");
2721b029f6bbSAndrew Thompson 		goto error;
2722b029f6bbSAndrew Thompson 	}
27233a3f90c6SAndrew Thompson 	return (ch);
27243a3f90c6SAndrew Thompson 
27253a3f90c6SAndrew Thompson error:
27263a3f90c6SAndrew Thompson 	uaudio_chan_free(ch);
27273a3f90c6SAndrew Thompson 	return (NULL);
27283a3f90c6SAndrew Thompson }
27293a3f90c6SAndrew Thompson 
27303a3f90c6SAndrew Thompson int
uaudio_chan_free(struct uaudio_chan * ch)27313a3f90c6SAndrew Thompson uaudio_chan_free(struct uaudio_chan *ch)
27323a3f90c6SAndrew Thompson {
27333a3f90c6SAndrew Thompson 	if (ch->buf != NULL) {
27343a3f90c6SAndrew Thompson 		free(ch->buf, M_DEVBUF);
27353a3f90c6SAndrew Thompson 		ch->buf = NULL;
27363a3f90c6SAndrew Thompson 	}
2737b4380da7SHans Petter Selasky 	usbd_transfer_unsetup(ch->xfer, UAUDIO_NCHANBUFS + 1);
27383a3f90c6SAndrew Thompson 
2739455a367fSHans Petter Selasky 	ch->num_alt = 0;
27403a3f90c6SAndrew Thompson 
27413a3f90c6SAndrew Thompson 	return (0);
27423a3f90c6SAndrew Thompson }
27433a3f90c6SAndrew Thompson 
27443a3f90c6SAndrew Thompson int
uaudio_chan_set_param_blocksize(struct uaudio_chan * ch,uint32_t blocksize)27453a3f90c6SAndrew Thompson uaudio_chan_set_param_blocksize(struct uaudio_chan *ch, uint32_t blocksize)
27463a3f90c6SAndrew Thompson {
2747455a367fSHans Petter Selasky 	uint32_t temp = 2 * uaudio_get_buffer_size(ch, ch->set_alt);
2748455a367fSHans Petter Selasky 	sndbuf_setup(ch->pcm_buf, ch->buf, temp);
2749455a367fSHans Petter Selasky 	return (temp / 2);
27503a3f90c6SAndrew Thompson }
27513a3f90c6SAndrew Thompson 
27523a3f90c6SAndrew Thompson int
uaudio_chan_set_param_fragments(struct uaudio_chan * ch,uint32_t blocksize,uint32_t blockcount)27533a3f90c6SAndrew Thompson uaudio_chan_set_param_fragments(struct uaudio_chan *ch, uint32_t blocksize,
27543a3f90c6SAndrew Thompson     uint32_t blockcount)
27553a3f90c6SAndrew Thompson {
27563a3f90c6SAndrew Thompson 	return (1);
27573a3f90c6SAndrew Thompson }
27583a3f90c6SAndrew Thompson 
27593a3f90c6SAndrew Thompson int
uaudio_chan_set_param_speed(struct uaudio_chan * ch,uint32_t speed)27603a3f90c6SAndrew Thompson uaudio_chan_set_param_speed(struct uaudio_chan *ch, uint32_t speed)
27613a3f90c6SAndrew Thompson {
276285bad582SHans Petter Selasky 	struct uaudio_softc *sc;
2763a9341f0fSFlorian Walpen 	uint8_t x, y;
2764455a367fSHans Petter Selasky 
276585bad582SHans Petter Selasky 	sc = ch->priv_sc;
276685bad582SHans Petter Selasky 
2767a9341f0fSFlorian Walpen 	for (x = 0, y = 1; y < ch->num_alt; y++) {
2768a9341f0fSFlorian Walpen 		/* prefer sample rate closer to and greater than requested */
2769a9341f0fSFlorian Walpen 		if ((ch->usb_alt[x].sample_rate < speed &&
2770a9341f0fSFlorian Walpen 		    ch->usb_alt[x].sample_rate < ch->usb_alt[y].sample_rate) ||
2771a9341f0fSFlorian Walpen 		    (speed <= ch->usb_alt[y].sample_rate &&
2772a9341f0fSFlorian Walpen 		    ch->usb_alt[y].sample_rate < ch->usb_alt[x].sample_rate))
2773a9341f0fSFlorian Walpen 			x = y;
27743a3f90c6SAndrew Thompson 	}
2775455a367fSHans Petter Selasky 
277685bad582SHans Petter Selasky 	usb_proc_explore_lock(sc->sc_udev);
2777455a367fSHans Petter Selasky 	ch->set_alt = x;
277885bad582SHans Petter Selasky 	usb_proc_explore_unlock(sc->sc_udev);
2779455a367fSHans Petter Selasky 
2780455a367fSHans Petter Selasky 	DPRINTF("Selecting alt %d\n", (int)x);
2781455a367fSHans Petter Selasky 
2782455a367fSHans Petter Selasky 	return (ch->usb_alt[x].sample_rate);
27833a3f90c6SAndrew Thompson }
27843a3f90c6SAndrew Thompson 
27853a3f90c6SAndrew Thompson int
uaudio_chan_getptr(struct uaudio_chan * ch)27863a3f90c6SAndrew Thompson uaudio_chan_getptr(struct uaudio_chan *ch)
27873a3f90c6SAndrew Thompson {
27883a3f90c6SAndrew Thompson 	return (ch->cur - ch->start);
27893a3f90c6SAndrew Thompson }
27903a3f90c6SAndrew Thompson 
27913a3f90c6SAndrew Thompson struct pcmchan_caps *
uaudio_chan_getcaps(struct uaudio_chan * ch)27923a3f90c6SAndrew Thompson uaudio_chan_getcaps(struct uaudio_chan *ch)
27933a3f90c6SAndrew Thompson {
27943a3f90c6SAndrew Thompson 	return (&ch->pcm_cap);
27953a3f90c6SAndrew Thompson }
27963a3f90c6SAndrew Thompson 
279790da2b28SAriff Abdullah static struct pcmchan_matrix uaudio_chan_matrix_swap_2_0 = {
279890da2b28SAriff Abdullah 	.id = SND_CHN_MATRIX_DRV,
279990da2b28SAriff Abdullah 	.channels = 2,
280090da2b28SAriff Abdullah 	.ext = 0,
280190da2b28SAriff Abdullah 	.map = {
280290da2b28SAriff Abdullah 		/* Right */
280390da2b28SAriff Abdullah 		[0] = {
280490da2b28SAriff Abdullah 			.type = SND_CHN_T_FR,
280590da2b28SAriff Abdullah 			.members =
280690da2b28SAriff Abdullah 			    SND_CHN_T_MASK_FR | SND_CHN_T_MASK_FC |
280790da2b28SAriff Abdullah 			    SND_CHN_T_MASK_LF | SND_CHN_T_MASK_BR |
280890da2b28SAriff Abdullah 			    SND_CHN_T_MASK_BC | SND_CHN_T_MASK_SR
280990da2b28SAriff Abdullah 		},
281090da2b28SAriff Abdullah 		/* Left */
281190da2b28SAriff Abdullah 		[1] = {
281290da2b28SAriff Abdullah 			.type = SND_CHN_T_FL,
281390da2b28SAriff Abdullah 			.members =
281490da2b28SAriff Abdullah 			    SND_CHN_T_MASK_FL | SND_CHN_T_MASK_FC |
281590da2b28SAriff Abdullah 			    SND_CHN_T_MASK_LF | SND_CHN_T_MASK_BL |
281690da2b28SAriff Abdullah 			    SND_CHN_T_MASK_BC | SND_CHN_T_MASK_SL
281790da2b28SAriff Abdullah 		},
281890da2b28SAriff Abdullah 		[2] = {
281990da2b28SAriff Abdullah 			.type = SND_CHN_T_MAX,
282090da2b28SAriff Abdullah 			.members = 0
282190da2b28SAriff Abdullah 		}
282290da2b28SAriff Abdullah 	},
282390da2b28SAriff Abdullah 	.mask = SND_CHN_T_MASK_FR | SND_CHN_T_MASK_FL,
282490da2b28SAriff Abdullah 	.offset = {  1,  0, -1, -1, -1, -1, -1, -1, -1,
282590da2b28SAriff Abdullah 		    -1, -1, -1, -1, -1, -1, -1, -1, -1  }
282690da2b28SAriff Abdullah };
282790da2b28SAriff Abdullah 
282890da2b28SAriff Abdullah struct pcmchan_matrix *
uaudio_chan_getmatrix(struct uaudio_chan * ch,uint32_t format)282990da2b28SAriff Abdullah uaudio_chan_getmatrix(struct uaudio_chan *ch, uint32_t format)
283090da2b28SAriff Abdullah {
283190da2b28SAriff Abdullah 	struct uaudio_softc *sc;
283290da2b28SAriff Abdullah 
283390da2b28SAriff Abdullah 	sc = ch->priv_sc;
283490da2b28SAriff Abdullah 
283590da2b28SAriff Abdullah 	if (sc != NULL && sc->sc_uq_audio_swap_lr != 0 &&
283690da2b28SAriff Abdullah 	    AFMT_CHANNEL(format) == 2)
283790da2b28SAriff Abdullah 		return (&uaudio_chan_matrix_swap_2_0);
283890da2b28SAriff Abdullah 
283990da2b28SAriff Abdullah 	return (feeder_matrix_format_map(format));
284090da2b28SAriff Abdullah }
284190da2b28SAriff Abdullah 
28423a3f90c6SAndrew Thompson int
uaudio_chan_set_param_format(struct uaudio_chan * ch,uint32_t format)28433a3f90c6SAndrew Thompson uaudio_chan_set_param_format(struct uaudio_chan *ch, uint32_t format)
28443a3f90c6SAndrew Thompson {
2845455a367fSHans Petter Selasky 	DPRINTF("Selecting format 0x%08x\n", (unsigned int)format);
28463a3f90c6SAndrew Thompson 	return (0);
28473a3f90c6SAndrew Thompson }
28483a3f90c6SAndrew Thompson 
284985bad582SHans Petter Selasky static void
uaudio_chan_reconfigure(struct uaudio_chan * ch,uint8_t operation)285031070b5bSHans Petter Selasky uaudio_chan_reconfigure(struct uaudio_chan *ch, uint8_t operation)
28513a3f90c6SAndrew Thompson {
2852455a367fSHans Petter Selasky 	struct uaudio_softc *sc = ch->priv_sc;
28533a3f90c6SAndrew Thompson 
285431070b5bSHans Petter Selasky 	/* Check for shutdown. */
285531070b5bSHans Petter Selasky 	if (ch->operation == CHAN_OP_DRAIN)
285631070b5bSHans Petter Selasky 		return;
285731070b5bSHans Petter Selasky 
285831070b5bSHans Petter Selasky 	/* Set next operation. */
285931070b5bSHans Petter Selasky 	ch->operation = operation;
286031070b5bSHans Petter Selasky 
286131070b5bSHans Petter Selasky 	/*
286231070b5bSHans Petter Selasky 	 * Because changing the alternate setting modifies the USB
286331070b5bSHans Petter Selasky 	 * configuration, this part must be executed from the USB
286431070b5bSHans Petter Selasky 	 * explore process.
286531070b5bSHans Petter Selasky 	 */
2866455a367fSHans Petter Selasky 	(void)usb_proc_explore_msignal(sc->sc_udev,
2867455a367fSHans Petter Selasky 	    &sc->sc_config_msg[0], &sc->sc_config_msg[1]);
2868455a367fSHans Petter Selasky }
28693a3f90c6SAndrew Thompson 
287085bad582SHans Petter Selasky static int
uaudio_chan_need_both(struct uaudio_chan * pchan,struct uaudio_chan * rchan)287186c9b3f3SHans Petter Selasky uaudio_chan_need_both(struct uaudio_chan *pchan, struct uaudio_chan *rchan)
287285bad582SHans Petter Selasky {
287386c9b3f3SHans Petter Selasky 	return (pchan->num_alt > 0 &&
287486c9b3f3SHans Petter Selasky 	    pchan->running != 0 &&
287586c9b3f3SHans Petter Selasky 	    uaudio_chan_is_async(pchan, pchan->set_alt) != 0 &&
287686c9b3f3SHans Petter Selasky 	    rchan->num_alt > 0 &&
287786c9b3f3SHans Petter Selasky 	    rchan->running == 0);
287885bad582SHans Petter Selasky }
287985bad582SHans Petter Selasky 
288085bad582SHans Petter Selasky static int
uaudio_chan_need_none(struct uaudio_chan * pchan,struct uaudio_chan * rchan)288186c9b3f3SHans Petter Selasky uaudio_chan_need_none(struct uaudio_chan *pchan, struct uaudio_chan *rchan)
288285bad582SHans Petter Selasky {
288386c9b3f3SHans Petter Selasky 	return (pchan->num_alt > 0 &&
288486c9b3f3SHans Petter Selasky 	    pchan->running == 0 &&
288586c9b3f3SHans Petter Selasky 	    rchan->num_alt > 0 &&
288686c9b3f3SHans Petter Selasky 	    rchan->running == 0);
288785bad582SHans Petter Selasky }
288885bad582SHans Petter Selasky 
288985bad582SHans Petter Selasky void
uaudio_chan_start(struct uaudio_chan * ch)289085bad582SHans Petter Selasky uaudio_chan_start(struct uaudio_chan *ch)
289185bad582SHans Petter Selasky {
289285bad582SHans Petter Selasky 	struct uaudio_softc *sc = ch->priv_sc;
289386c9b3f3SHans Petter Selasky 	unsigned i = uaudio_get_child_index_by_chan(sc, ch);
289485bad582SHans Petter Selasky 
289585bad582SHans Petter Selasky 	/* make operation atomic */
289685bad582SHans Petter Selasky 	usb_proc_explore_lock(sc->sc_udev);
289785bad582SHans Petter Selasky 
289885bad582SHans Petter Selasky 	/* check if not running */
289985bad582SHans Petter Selasky 	if (ch->running == 0) {
290085bad582SHans Petter Selasky 		uint32_t temp;
290185bad582SHans Petter Selasky 
290285bad582SHans Petter Selasky 		/* get current buffer size */
290385bad582SHans Petter Selasky 		temp = 2 * uaudio_get_buffer_size(ch, ch->set_alt);
290485bad582SHans Petter Selasky 
290585bad582SHans Petter Selasky 		/* set running flag */
290685bad582SHans Petter Selasky 		ch->running = 1;
290785bad582SHans Petter Selasky 
290885bad582SHans Petter Selasky 		/* ensure the hardware buffer is reset */
290985bad582SHans Petter Selasky 		ch->start = ch->buf;
291085bad582SHans Petter Selasky 		ch->end = ch->buf + temp;
291185bad582SHans Petter Selasky 		ch->cur = ch->buf;
291285bad582SHans Petter Selasky 
291386c9b3f3SHans Petter Selasky 		if (uaudio_chan_need_both(
291486c9b3f3SHans Petter Selasky 		    &sc->sc_play_chan[i],
291586c9b3f3SHans Petter Selasky 		    &sc->sc_rec_chan[i])) {
291685bad582SHans Petter Selasky 			/*
291785bad582SHans Petter Selasky 			 * Start both endpoints because of need for
291885bad582SHans Petter Selasky 			 * jitter information:
291985bad582SHans Petter Selasky 			 */
292031070b5bSHans Petter Selasky 			uaudio_chan_reconfigure(&sc->sc_rec_chan[i], CHAN_OP_START);
292131070b5bSHans Petter Selasky 			uaudio_chan_reconfigure(&sc->sc_play_chan[i], CHAN_OP_START);
292285bad582SHans Petter Selasky 		} else {
292331070b5bSHans Petter Selasky 			uaudio_chan_reconfigure(ch, CHAN_OP_START);
292485bad582SHans Petter Selasky 		}
292585bad582SHans Petter Selasky 	}
292685bad582SHans Petter Selasky 
292785bad582SHans Petter Selasky 	/* exit atomic operation */
292885bad582SHans Petter Selasky 	usb_proc_explore_unlock(sc->sc_udev);
292985bad582SHans Petter Selasky }
293085bad582SHans Petter Selasky 
293185bad582SHans Petter Selasky void
uaudio_chan_stop(struct uaudio_chan * ch)293285bad582SHans Petter Selasky uaudio_chan_stop(struct uaudio_chan *ch)
293385bad582SHans Petter Selasky {
293485bad582SHans Petter Selasky 	struct uaudio_softc *sc = ch->priv_sc;
293586c9b3f3SHans Petter Selasky 	unsigned i = uaudio_get_child_index_by_chan(sc, ch);
293685bad582SHans Petter Selasky 
293785bad582SHans Petter Selasky 	/* make operation atomic */
293885bad582SHans Petter Selasky 	usb_proc_explore_lock(sc->sc_udev);
293985bad582SHans Petter Selasky 
294085bad582SHans Petter Selasky 	/* check if running */
294185bad582SHans Petter Selasky 	if (ch->running != 0) {
294285bad582SHans Petter Selasky 		/* clear running flag */
294385bad582SHans Petter Selasky 		ch->running = 0;
294485bad582SHans Petter Selasky 
294586c9b3f3SHans Petter Selasky 		if (uaudio_chan_need_both(
294686c9b3f3SHans Petter Selasky 		    &sc->sc_play_chan[i],
294786c9b3f3SHans Petter Selasky 		    &sc->sc_rec_chan[i])) {
294885bad582SHans Petter Selasky 			/*
294985bad582SHans Petter Selasky 			 * Leave the endpoints running because we need
295085bad582SHans Petter Selasky 			 * information about jitter!
295185bad582SHans Petter Selasky 			 */
295286c9b3f3SHans Petter Selasky 		} else if (uaudio_chan_need_none(
295386c9b3f3SHans Petter Selasky 		    &sc->sc_play_chan[i],
295486c9b3f3SHans Petter Selasky 		    &sc->sc_rec_chan[i])) {
295585bad582SHans Petter Selasky 			/*
295685bad582SHans Petter Selasky 			 * Stop both endpoints in case the one was used for
295785bad582SHans Petter Selasky 			 * jitter information:
295885bad582SHans Petter Selasky 			 */
295931070b5bSHans Petter Selasky 			uaudio_chan_reconfigure(&sc->sc_rec_chan[i], CHAN_OP_STOP);
296031070b5bSHans Petter Selasky 			uaudio_chan_reconfigure(&sc->sc_play_chan[i], CHAN_OP_STOP);
296185bad582SHans Petter Selasky 		} else {
296231070b5bSHans Petter Selasky 			uaudio_chan_reconfigure(ch, CHAN_OP_STOP);
296385bad582SHans Petter Selasky 		}
296485bad582SHans Petter Selasky 	}
296585bad582SHans Petter Selasky 
296685bad582SHans Petter Selasky 	/* exit atomic operation */
296785bad582SHans Petter Selasky 	usb_proc_explore_unlock(sc->sc_udev);
29683a3f90c6SAndrew Thompson }
29693a3f90c6SAndrew Thompson 
29703a3f90c6SAndrew Thompson /*========================================================================*
29713a3f90c6SAndrew Thompson  * AC - Audio Controller - routines
29723a3f90c6SAndrew Thompson  *========================================================================*/
29733a3f90c6SAndrew Thompson 
2974902514f6SHans Petter Selasky static int
uaudio_mixer_sysctl_handler(SYSCTL_HANDLER_ARGS)2975902514f6SHans Petter Selasky uaudio_mixer_sysctl_handler(SYSCTL_HANDLER_ARGS)
2976902514f6SHans Petter Selasky {
2977902514f6SHans Petter Selasky 	struct uaudio_softc *sc;
2978902514f6SHans Petter Selasky 	struct uaudio_mixer_node *pmc;
2979902514f6SHans Petter Selasky 	int hint;
2980902514f6SHans Petter Selasky 	int error;
2981902514f6SHans Petter Selasky 	int temp = 0;
2982902514f6SHans Petter Selasky 	int chan = 0;
2983902514f6SHans Petter Selasky 
2984902514f6SHans Petter Selasky 	sc = (struct uaudio_softc *)oidp->oid_arg1;
2985902514f6SHans Petter Selasky 	hint = oidp->oid_arg2;
2986902514f6SHans Petter Selasky 
298786c9b3f3SHans Petter Selasky 	if (sc->sc_child[0].mixer_lock == NULL)
2988902514f6SHans Petter Selasky 		return (ENXIO);
2989902514f6SHans Petter Selasky 
2990902514f6SHans Petter Selasky 	/* lookup mixer node */
2991902514f6SHans Petter Selasky 
299286c9b3f3SHans Petter Selasky 	mtx_lock(sc->sc_child[0].mixer_lock);
2993902514f6SHans Petter Selasky 	for (pmc = sc->sc_mixer_root; pmc != NULL; pmc = pmc->next) {
2994902514f6SHans Petter Selasky 		for (chan = 0; chan != (int)pmc->nchan; chan++) {
2995902514f6SHans Petter Selasky 			if (pmc->wValue[chan] != -1 &&
2996902514f6SHans Petter Selasky 			    pmc->wValue[chan] == hint) {
2997902514f6SHans Petter Selasky 				temp = pmc->wData[chan];
2998902514f6SHans Petter Selasky 				goto found;
2999902514f6SHans Petter Selasky 			}
3000902514f6SHans Petter Selasky 		}
3001902514f6SHans Petter Selasky 	}
3002902514f6SHans Petter Selasky found:
300386c9b3f3SHans Petter Selasky 	mtx_unlock(sc->sc_child[0].mixer_lock);
3004902514f6SHans Petter Selasky 
3005902514f6SHans Petter Selasky 	error = sysctl_handle_int(oidp, &temp, 0, req);
3006902514f6SHans Petter Selasky 	if (error != 0 || req->newptr == NULL)
3007902514f6SHans Petter Selasky 		return (error);
3008902514f6SHans Petter Selasky 
3009902514f6SHans Petter Selasky 	/* update mixer value */
3010902514f6SHans Petter Selasky 
301186c9b3f3SHans Petter Selasky 	mtx_lock(sc->sc_child[0].mixer_lock);
3012902514f6SHans Petter Selasky 	if (pmc != NULL &&
3013902514f6SHans Petter Selasky 	    temp >= pmc->minval &&
3014902514f6SHans Petter Selasky 	    temp <= pmc->maxval) {
3015902514f6SHans Petter Selasky 		pmc->wData[chan] = temp;
3016902514f6SHans Petter Selasky 		pmc->update[(chan / 8)] |= (1 << (chan % 8));
3017902514f6SHans Petter Selasky 
3018902514f6SHans Petter Selasky 		/* start the transfer, if not already started */
3019902514f6SHans Petter Selasky 		usbd_transfer_start(sc->sc_mixer_xfer[0]);
3020902514f6SHans Petter Selasky 	}
302186c9b3f3SHans Petter Selasky 	mtx_unlock(sc->sc_child[0].mixer_lock);
3022902514f6SHans Petter Selasky 
3023902514f6SHans Petter Selasky 	return (0);
3024902514f6SHans Petter Selasky }
3025902514f6SHans Petter Selasky 
3026902514f6SHans Petter Selasky static void
uaudio_mixer_ctl_free(struct uaudio_softc * sc)3027902514f6SHans Petter Selasky uaudio_mixer_ctl_free(struct uaudio_softc *sc)
3028902514f6SHans Petter Selasky {
3029902514f6SHans Petter Selasky 	struct uaudio_mixer_node *p_mc;
3030902514f6SHans Petter Selasky 
3031902514f6SHans Petter Selasky 	while ((p_mc = sc->sc_mixer_root) != NULL) {
3032902514f6SHans Petter Selasky 		sc->sc_mixer_root = p_mc->next;
3033902514f6SHans Petter Selasky 		free(p_mc, M_USBDEV);
3034902514f6SHans Petter Selasky 	}
3035902514f6SHans Petter Selasky }
3036902514f6SHans Petter Selasky 
3037902514f6SHans Petter Selasky static void
uaudio_mixer_register_sysctl(struct uaudio_softc * sc,device_t dev,unsigned index)303886c9b3f3SHans Petter Selasky uaudio_mixer_register_sysctl(struct uaudio_softc *sc, device_t dev,
303986c9b3f3SHans Petter Selasky     unsigned index)
3040902514f6SHans Petter Selasky {
3041902514f6SHans Petter Selasky 	struct uaudio_mixer_node *pmc;
3042902514f6SHans Petter Selasky 	struct sysctl_oid *mixer_tree;
3043902514f6SHans Petter Selasky 	struct sysctl_oid *control_tree;
3044902514f6SHans Petter Selasky 	char buf[32];
3045902514f6SHans Petter Selasky 	int chan;
3046902514f6SHans Petter Selasky 	int n;
3047902514f6SHans Petter Selasky 
304886c9b3f3SHans Petter Selasky 	if (index != 0)
304986c9b3f3SHans Petter Selasky 		return;
305086c9b3f3SHans Petter Selasky 
3051ff4d5953SHans Petter Selasky 	mixer_tree = SYSCTL_ADD_NODE(device_get_sysctl_ctx(dev),
3052902514f6SHans Petter Selasky 	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "mixer",
3053f8d2b1f3SPawel Biernacki 	    CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "");
3054902514f6SHans Petter Selasky 
3055902514f6SHans Petter Selasky 	if (mixer_tree == NULL)
3056902514f6SHans Petter Selasky 		return;
3057902514f6SHans Petter Selasky 
3058902514f6SHans Petter Selasky 	for (n = 0, pmc = sc->sc_mixer_root; pmc != NULL;
3059902514f6SHans Petter Selasky 	    pmc = pmc->next, n++) {
3060902514f6SHans Petter Selasky 		for (chan = 0; chan < pmc->nchan; chan++) {
3061902514f6SHans Petter Selasky 			if (pmc->nchan > 1) {
3062902514f6SHans Petter Selasky 				snprintf(buf, sizeof(buf), "%s_%d_%d",
3063902514f6SHans Petter Selasky 				    pmc->name, n, chan);
3064902514f6SHans Petter Selasky 			} else {
3065902514f6SHans Petter Selasky 				snprintf(buf, sizeof(buf), "%s_%d",
3066902514f6SHans Petter Selasky 				    pmc->name, n);
3067902514f6SHans Petter Selasky 			}
3068902514f6SHans Petter Selasky 
3069ff4d5953SHans Petter Selasky 			control_tree = SYSCTL_ADD_NODE(device_get_sysctl_ctx(dev),
3070902514f6SHans Petter Selasky 			    SYSCTL_CHILDREN(mixer_tree), OID_AUTO, buf,
3071f8d2b1f3SPawel Biernacki 			    CTLFLAG_RD | CTLFLAG_MPSAFE, NULL,
3072f8d2b1f3SPawel Biernacki 			    "Mixer control nodes");
3073902514f6SHans Petter Selasky 
3074902514f6SHans Petter Selasky 			if (control_tree == NULL)
3075902514f6SHans Petter Selasky 				continue;
3076902514f6SHans Petter Selasky 
3077ff4d5953SHans Petter Selasky 			SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
3078902514f6SHans Petter Selasky 			    SYSCTL_CHILDREN(control_tree),
3079f8d2b1f3SPawel Biernacki 			    OID_AUTO, "val",
3080f8d2b1f3SPawel Biernacki 			    CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
3081f8d2b1f3SPawel Biernacki 			    sc, pmc->wValue[chan],
3082902514f6SHans Petter Selasky 			    uaudio_mixer_sysctl_handler, "I", "Current value");
3083902514f6SHans Petter Selasky 
3084ff4d5953SHans Petter Selasky 			SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
3085902514f6SHans Petter Selasky 			    SYSCTL_CHILDREN(control_tree),
3086902514f6SHans Petter Selasky 			    OID_AUTO, "min", CTLFLAG_RD, 0, pmc->minval,
3087902514f6SHans Petter Selasky 			    "Minimum value");
3088902514f6SHans Petter Selasky 
3089ff4d5953SHans Petter Selasky 			SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
3090902514f6SHans Petter Selasky 			    SYSCTL_CHILDREN(control_tree),
3091902514f6SHans Petter Selasky 			    OID_AUTO, "max", CTLFLAG_RD, 0, pmc->maxval,
3092902514f6SHans Petter Selasky 			    "Maximum value");
3093902514f6SHans Petter Selasky 
3094ff4d5953SHans Petter Selasky 			SYSCTL_ADD_STRING(device_get_sysctl_ctx(dev),
3095902514f6SHans Petter Selasky 			    SYSCTL_CHILDREN(control_tree),
3096902514f6SHans Petter Selasky 			    OID_AUTO, "desc", CTLFLAG_RD, pmc->desc, 0,
3097902514f6SHans Petter Selasky 			    "Description");
3098902514f6SHans Petter Selasky 		}
3099902514f6SHans Petter Selasky 	}
3100902514f6SHans Petter Selasky }
3101902514f6SHans Petter Selasky 
3102ff4d5953SHans Petter Selasky /* M-Audio FastTrack Ultra Mixer Description */
3103ff4d5953SHans Petter Selasky /* Origin: Linux USB Audio driver */
3104ff4d5953SHans Petter Selasky static void
uaudio_mixer_controls_create_ftu(struct uaudio_softc * sc)3105ff4d5953SHans Petter Selasky uaudio_mixer_controls_create_ftu(struct uaudio_softc *sc)
3106ff4d5953SHans Petter Selasky {
3107ff4d5953SHans Petter Selasky 	int chx;
3108ff4d5953SHans Petter Selasky 	int chy;
3109ff4d5953SHans Petter Selasky 
3110f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
3111f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no);
3112f7e62ad0SHans Petter Selasky 	MIX(sc).wValue[0] = MAKE_WORD(8, 0);
3113f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_UNSIGNED_16;
3114f7e62ad0SHans Petter Selasky 	MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
3115f7e62ad0SHans Petter Selasky 	MIX(sc).name = "effect";
3116f7e62ad0SHans Petter Selasky 	MIX(sc).minval = 0;
3117f7e62ad0SHans Petter Selasky 	MIX(sc).maxval = 7;
3118f7e62ad0SHans Petter Selasky 	MIX(sc).mul = 7;
3119f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
3120f7e62ad0SHans Petter Selasky 	MIX(sc).update[0] = 1;
3121f7e62ad0SHans Petter Selasky 	strlcpy(MIX(sc).desc, "Room1,2,3,Hall1,2,Plate,Delay,Echo", sizeof(MIX(sc).desc));
3122f7e62ad0SHans Petter Selasky 	uaudio_mixer_add_ctl_sub(sc, &MIX(sc));
3123ff4d5953SHans Petter Selasky 
3124f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
3125f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(5, sc->sc_mixer_iface_no);
3126ff4d5953SHans Petter Selasky 
3127ff4d5953SHans Petter Selasky 	for (chx = 0; chx != 8; chx++) {
3128ff4d5953SHans Petter Selasky 		for (chy = 0; chy != 8; chy++) {
3129f7e62ad0SHans Petter Selasky 			MIX(sc).wValue[0] = MAKE_WORD(chx + 1, chy + 1);
3130f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_16;
3131f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
3132f7e62ad0SHans Petter Selasky 			MIX(sc).name = "mix_rec";
3133f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = 1;
3134f7e62ad0SHans Petter Selasky 			MIX(sc).update[0] = 1;
3135f7e62ad0SHans Petter Selasky 			MIX(sc).val_default = 0;
3136f7e62ad0SHans Petter Selasky 			snprintf(MIX(sc).desc, sizeof(MIX(sc).desc),
3137ff4d5953SHans Petter Selasky 			    "AIn%d - Out%d Record Volume", chy + 1, chx + 1);
3138ff4d5953SHans Petter Selasky 
3139f7e62ad0SHans Petter Selasky 			uaudio_mixer_add_ctl(sc, &MIX(sc));
3140ff4d5953SHans Petter Selasky 
3141f7e62ad0SHans Petter Selasky 			MIX(sc).wValue[0] = MAKE_WORD(chx + 1, chy + 1 + 8);
3142f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_16;
3143f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
3144f7e62ad0SHans Petter Selasky 			MIX(sc).name = "mix_play";
3145f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = 1;
3146f7e62ad0SHans Petter Selasky 			MIX(sc).update[0] = 1;
3147f7e62ad0SHans Petter Selasky 			MIX(sc).val_default = (chx == chy) ? 2 : 0;
3148f7e62ad0SHans Petter Selasky 			snprintf(MIX(sc).desc, sizeof(MIX(sc).desc),
3149ff4d5953SHans Petter Selasky 			    "DIn%d - Out%d Playback Volume", chy + 1, chx + 1);
3150ff4d5953SHans Petter Selasky 
3151f7e62ad0SHans Petter Selasky 			uaudio_mixer_add_ctl(sc, &MIX(sc));
3152ff4d5953SHans Petter Selasky 		}
3153ff4d5953SHans Petter Selasky 	}
3154ff4d5953SHans Petter Selasky 
3155f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
3156f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no);
3157f7e62ad0SHans Petter Selasky 	MIX(sc).wValue[0] = MAKE_WORD(2, 0);
3158f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_SIGNED_8;
3159f7e62ad0SHans Petter Selasky 	MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
3160f7e62ad0SHans Petter Selasky 	MIX(sc).name = "effect_vol";
3161f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
3162f7e62ad0SHans Petter Selasky 	MIX(sc).update[0] = 1;
3163f7e62ad0SHans Petter Selasky 	MIX(sc).minval = 0;
3164f7e62ad0SHans Petter Selasky 	MIX(sc).maxval = 0x7f;
3165f7e62ad0SHans Petter Selasky 	MIX(sc).mul = 0x7f;
3166f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
3167f7e62ad0SHans Petter Selasky 	MIX(sc).update[0] = 1;
3168f7e62ad0SHans Petter Selasky 	strlcpy(MIX(sc).desc, "Effect Volume", sizeof(MIX(sc).desc));
3169f7e62ad0SHans Petter Selasky 	uaudio_mixer_add_ctl_sub(sc, &MIX(sc));
3170ff4d5953SHans Petter Selasky 
3171f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
3172f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no);
3173f7e62ad0SHans Petter Selasky 	MIX(sc).wValue[0] = MAKE_WORD(3, 0);
3174f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_SIGNED_16;
3175f7e62ad0SHans Petter Selasky 	MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
3176f7e62ad0SHans Petter Selasky 	MIX(sc).name = "effect_dur";
3177f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
3178f7e62ad0SHans Petter Selasky 	MIX(sc).update[0] = 1;
3179f7e62ad0SHans Petter Selasky 	MIX(sc).minval = 0;
3180f7e62ad0SHans Petter Selasky 	MIX(sc).maxval = 0x7f00;
3181f7e62ad0SHans Petter Selasky 	MIX(sc).mul = 0x7f00;
3182f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
3183f7e62ad0SHans Petter Selasky 	MIX(sc).update[0] = 1;
3184f7e62ad0SHans Petter Selasky 	strlcpy(MIX(sc).desc, "Effect Duration", sizeof(MIX(sc).desc));
3185f7e62ad0SHans Petter Selasky 	uaudio_mixer_add_ctl_sub(sc, &MIX(sc));
3186ff4d5953SHans Petter Selasky 
3187f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
3188f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(6, sc->sc_mixer_iface_no);
3189f7e62ad0SHans Petter Selasky 	MIX(sc).wValue[0] = MAKE_WORD(4, 0);
3190f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_SIGNED_8;
3191f7e62ad0SHans Petter Selasky 	MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
3192f7e62ad0SHans Petter Selasky 	MIX(sc).name = "effect_fb";
3193f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
3194f7e62ad0SHans Petter Selasky 	MIX(sc).update[0] = 1;
3195f7e62ad0SHans Petter Selasky 	MIX(sc).minval = 0;
3196f7e62ad0SHans Petter Selasky 	MIX(sc).maxval = 0x7f;
3197f7e62ad0SHans Petter Selasky 	MIX(sc).mul = 0x7f;
3198f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
3199f7e62ad0SHans Petter Selasky 	MIX(sc).update[0] = 1;
3200f7e62ad0SHans Petter Selasky 	strlcpy(MIX(sc).desc, "Effect Feedback Volume", sizeof(MIX(sc).desc));
3201f7e62ad0SHans Petter Selasky 	uaudio_mixer_add_ctl_sub(sc, &MIX(sc));
3202ff4d5953SHans Petter Selasky 
3203f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
3204f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(7, sc->sc_mixer_iface_no);
3205ff4d5953SHans Petter Selasky 	for (chy = 0; chy != 4; chy++) {
3206f7e62ad0SHans Petter Selasky 		MIX(sc).wValue[0] = MAKE_WORD(7, chy + 1);
3207f7e62ad0SHans Petter Selasky 		MIX(sc).type = MIX_SIGNED_16;
3208f7e62ad0SHans Petter Selasky 		MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
3209f7e62ad0SHans Petter Selasky 		MIX(sc).name = "effect_ret";
3210f7e62ad0SHans Petter Selasky 		MIX(sc).nchan = 1;
3211f7e62ad0SHans Petter Selasky 		MIX(sc).update[0] = 1;
3212f7e62ad0SHans Petter Selasky 		snprintf(MIX(sc).desc, sizeof(MIX(sc).desc),
3213ff4d5953SHans Petter Selasky 		    "Effect Return %d Volume", chy + 1);
3214ff4d5953SHans Petter Selasky 
3215f7e62ad0SHans Petter Selasky 		uaudio_mixer_add_ctl(sc, &MIX(sc));
3216ff4d5953SHans Petter Selasky 	}
3217ff4d5953SHans Petter Selasky 
3218f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
3219f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(5, sc->sc_mixer_iface_no);
3220ff4d5953SHans Petter Selasky 
3221ff4d5953SHans Petter Selasky 	for (chy = 0; chy != 8; chy++) {
3222f7e62ad0SHans Petter Selasky 		MIX(sc).wValue[0] = MAKE_WORD(9, chy + 1);
3223f7e62ad0SHans Petter Selasky 		MIX(sc).type = MIX_SIGNED_16;
3224f7e62ad0SHans Petter Selasky 		MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
3225f7e62ad0SHans Petter Selasky 		MIX(sc).name = "effect_send";
3226f7e62ad0SHans Petter Selasky 		MIX(sc).nchan = 1;
3227f7e62ad0SHans Petter Selasky 		MIX(sc).update[0] = 1;
3228f7e62ad0SHans Petter Selasky 		snprintf(MIX(sc).desc, sizeof(MIX(sc).desc),
3229ff4d5953SHans Petter Selasky 		    "Effect Send AIn%d Volume", chy + 1);
3230ff4d5953SHans Petter Selasky 
3231f7e62ad0SHans Petter Selasky 		uaudio_mixer_add_ctl(sc, &MIX(sc));
3232ff4d5953SHans Petter Selasky 
3233113336eeSHans Petter Selasky 		MIX(sc).wValue[0] = MAKE_WORD(9, chy + 1 + 8);
3234f7e62ad0SHans Petter Selasky 		MIX(sc).type = MIX_SIGNED_16;
3235f7e62ad0SHans Petter Selasky 		MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
3236f7e62ad0SHans Petter Selasky 		MIX(sc).name = "effect_send";
3237f7e62ad0SHans Petter Selasky 		MIX(sc).nchan = 1;
3238f7e62ad0SHans Petter Selasky 		MIX(sc).update[0] = 1;
3239f7e62ad0SHans Petter Selasky 		snprintf(MIX(sc).desc, sizeof(MIX(sc).desc),
3240113336eeSHans Petter Selasky 		    "Effect Send DIn%d Volume", chy + 1);
3241ff4d5953SHans Petter Selasky 
3242f7e62ad0SHans Petter Selasky 		uaudio_mixer_add_ctl(sc, &MIX(sc));
3243ff4d5953SHans Petter Selasky 	}
3244ff4d5953SHans Petter Selasky }
3245ff4d5953SHans Petter Selasky 
3246902514f6SHans Petter Selasky static void
uaudio_mixer_reload_all(struct uaudio_softc * sc)3247902514f6SHans Petter Selasky uaudio_mixer_reload_all(struct uaudio_softc *sc)
3248902514f6SHans Petter Selasky {
3249902514f6SHans Petter Selasky 	struct uaudio_mixer_node *pmc;
3250902514f6SHans Petter Selasky 	int chan;
3251902514f6SHans Petter Selasky 
325286c9b3f3SHans Petter Selasky 	if (sc->sc_child[0].mixer_lock == NULL)
3253902514f6SHans Petter Selasky 		return;
3254902514f6SHans Petter Selasky 
325586c9b3f3SHans Petter Selasky 	mtx_lock(sc->sc_child[0].mixer_lock);
3256902514f6SHans Petter Selasky 	for (pmc = sc->sc_mixer_root; pmc != NULL; pmc = pmc->next) {
3257ff4d5953SHans Petter Selasky 		/* use reset defaults for non-oss controlled settings */
3258ff4d5953SHans Petter Selasky 		if (pmc->ctl == SOUND_MIXER_NRDEVICES)
3259ff4d5953SHans Petter Selasky 			continue;
3260902514f6SHans Petter Selasky 		for (chan = 0; chan < pmc->nchan; chan++)
3261902514f6SHans Petter Selasky 			pmc->update[chan / 8] |= (1 << (chan % 8));
3262902514f6SHans Petter Selasky 	}
3263902514f6SHans Petter Selasky 	usbd_transfer_start(sc->sc_mixer_xfer[0]);
326476b71212SHans Petter Selasky 
326576b71212SHans Petter Selasky 	/* start HID volume keys, if any */
326676b71212SHans Petter Selasky 	usbd_transfer_start(sc->sc_hid.xfer[0]);
326786c9b3f3SHans Petter Selasky 	mtx_unlock(sc->sc_child[0].mixer_lock);
3268902514f6SHans Petter Selasky }
3269902514f6SHans Petter Selasky 
32703a3f90c6SAndrew Thompson static void
uaudio_mixer_add_ctl_sub(struct uaudio_softc * sc,struct uaudio_mixer_node * mc)32713a3f90c6SAndrew Thompson uaudio_mixer_add_ctl_sub(struct uaudio_softc *sc, struct uaudio_mixer_node *mc)
32723a3f90c6SAndrew Thompson {
32733a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *p_mc_new =
32743a3f90c6SAndrew Thompson 	    malloc(sizeof(*p_mc_new), M_USBDEV, M_WAITOK);
3275ff4d5953SHans Petter Selasky 	int ch;
32763a3f90c6SAndrew Thompson 
32776f068a43SHans Petter Selasky 	if (p_mc_new != NULL) {
32786f068a43SHans Petter Selasky 		memcpy(p_mc_new, mc, sizeof(*p_mc_new));
32793a3f90c6SAndrew Thompson 		p_mc_new->next = sc->sc_mixer_root;
32803a3f90c6SAndrew Thompson 		sc->sc_mixer_root = p_mc_new;
32813a3f90c6SAndrew Thompson 		sc->sc_mixer_count++;
3282ff4d5953SHans Petter Selasky 
3283ff4d5953SHans Petter Selasky 		/* set default value for all channels */
3284ff4d5953SHans Petter Selasky 		for (ch = 0; ch < p_mc_new->nchan; ch++) {
3285ff4d5953SHans Petter Selasky 			switch (p_mc_new->val_default) {
3286ff4d5953SHans Petter Selasky 			case 1:
328758e8ac5cSHans Petter Selasky 				/* 50% */
3288ff4d5953SHans Petter Selasky 				p_mc_new->wData[ch] = (p_mc_new->maxval + p_mc_new->minval) / 2;
3289ff4d5953SHans Petter Selasky 				break;
3290ff4d5953SHans Petter Selasky 			case 2:
329158e8ac5cSHans Petter Selasky 				/* 100% */
3292ff4d5953SHans Petter Selasky 				p_mc_new->wData[ch] = p_mc_new->maxval;
3293ff4d5953SHans Petter Selasky 				break;
3294ff4d5953SHans Petter Selasky 			default:
329558e8ac5cSHans Petter Selasky 				/* 0% */
3296ff4d5953SHans Petter Selasky 				p_mc_new->wData[ch] = p_mc_new->minval;
3297ff4d5953SHans Petter Selasky 				break;
3298ff4d5953SHans Petter Selasky 			}
3299ff4d5953SHans Petter Selasky 		}
33003a3f90c6SAndrew Thompson 	} else {
33013a3f90c6SAndrew Thompson 		DPRINTF("out of memory\n");
33023a3f90c6SAndrew Thompson 	}
33033a3f90c6SAndrew Thompson }
33043a3f90c6SAndrew Thompson 
33053a3f90c6SAndrew Thompson static void
uaudio_mixer_add_ctl(struct uaudio_softc * sc,struct uaudio_mixer_node * mc)33063a3f90c6SAndrew Thompson uaudio_mixer_add_ctl(struct uaudio_softc *sc, struct uaudio_mixer_node *mc)
33073a3f90c6SAndrew Thompson {
33083a3f90c6SAndrew Thompson 	int32_t res;
33093a3f90c6SAndrew Thompson 
33103a3f90c6SAndrew Thompson 	DPRINTF("adding %d\n", mc->ctl);
33113a3f90c6SAndrew Thompson 
33123a3f90c6SAndrew Thompson 	if (mc->type == MIX_ON_OFF) {
33133a3f90c6SAndrew Thompson 		mc->minval = 0;
33143a3f90c6SAndrew Thompson 		mc->maxval = 1;
33153a3f90c6SAndrew Thompson 	} else if (mc->type == MIX_SELECTOR) {
33163a3f90c6SAndrew Thompson 	} else {
33173a3f90c6SAndrew Thompson 		/* determine min and max values */
33183a3f90c6SAndrew Thompson 
3319e2524b2eSHans Petter Selasky 		mc->minval = uaudio_mixer_get(sc->sc_udev,
3320e2524b2eSHans Petter Selasky 		    sc->sc_audio_rev, GET_MIN, mc);
3321e2524b2eSHans Petter Selasky 		mc->maxval = uaudio_mixer_get(sc->sc_udev,
3322e2524b2eSHans Petter Selasky 		    sc->sc_audio_rev, GET_MAX, mc);
33233a3f90c6SAndrew Thompson 
3324b029f6bbSAndrew Thompson 		/* check if max and min was swapped */
33253a3f90c6SAndrew Thompson 
33263a3f90c6SAndrew Thompson 		if (mc->maxval < mc->minval) {
3327b029f6bbSAndrew Thompson 			res = mc->maxval;
33283a3f90c6SAndrew Thompson 			mc->maxval = mc->minval;
3329b029f6bbSAndrew Thompson 			mc->minval = res;
33303a3f90c6SAndrew Thompson 		}
3331b029f6bbSAndrew Thompson 
3332b029f6bbSAndrew Thompson 		/* compute value range */
3333b029f6bbSAndrew Thompson 		mc->mul = mc->maxval - mc->minval;
3334b029f6bbSAndrew Thompson 		if (mc->mul == 0)
3335b029f6bbSAndrew Thompson 			mc->mul = 1;
3336b029f6bbSAndrew Thompson 
3337b029f6bbSAndrew Thompson 		/* compute value alignment */
3338e2524b2eSHans Petter Selasky 		res = uaudio_mixer_get(sc->sc_udev,
3339e2524b2eSHans Petter Selasky 		    sc->sc_audio_rev, GET_RES, mc);
33407fb43570SAndrew Thompson 
33417fb43570SAndrew Thompson 		DPRINTF("Resolution = %d\n", (int)res);
3342b029f6bbSAndrew Thompson 	}
3343b029f6bbSAndrew Thompson 
33443a3f90c6SAndrew Thompson 	uaudio_mixer_add_ctl_sub(sc, mc);
33453a3f90c6SAndrew Thompson 
3346b850ecc1SAndrew Thompson #ifdef USB_DEBUG
33473a3f90c6SAndrew Thompson 	if (uaudio_debug > 2) {
33483a3f90c6SAndrew Thompson 		uint8_t i;
33493a3f90c6SAndrew Thompson 
33503a3f90c6SAndrew Thompson 		for (i = 0; i < mc->nchan; i++) {
33513a3f90c6SAndrew Thompson 			DPRINTF("[mix] wValue=%04x\n", mc->wValue[0]);
33523a3f90c6SAndrew Thompson 		}
33533a3f90c6SAndrew Thompson 		DPRINTF("[mix] wIndex=%04x type=%d ctl='%d' "
33543a3f90c6SAndrew Thompson 		    "min=%d max=%d\n",
33553a3f90c6SAndrew Thompson 		    mc->wIndex, mc->type, mc->ctl,
33563a3f90c6SAndrew Thompson 		    mc->minval, mc->maxval);
33573a3f90c6SAndrew Thompson 	}
33583a3f90c6SAndrew Thompson #endif
33593a3f90c6SAndrew Thompson }
33603a3f90c6SAndrew Thompson 
33613a3f90c6SAndrew Thompson static void
uaudio_mixer_add_mixer(struct uaudio_softc * sc,const struct uaudio_terminal_node * iot,int id)33623a3f90c6SAndrew Thompson uaudio_mixer_add_mixer(struct uaudio_softc *sc,
33633a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
33643a3f90c6SAndrew Thompson {
3365e2524b2eSHans Petter Selasky 	const struct usb_audio_mixer_unit_0 *d0 = iot[id].u.mu_v1;
33664c21be9bSRebecca Cran 	const struct usb_audio_mixer_unit_1 *d1;
33673a3f90c6SAndrew Thompson 
33683a3f90c6SAndrew Thompson 	uint32_t bno;			/* bit number */
33693a3f90c6SAndrew Thompson 	uint32_t p;			/* bit number accumulator */
33703a3f90c6SAndrew Thompson 	uint32_t mo;			/* matching outputs */
33713a3f90c6SAndrew Thompson 	uint32_t mc;			/* matching channels */
33723a3f90c6SAndrew Thompson 	uint32_t ichs;			/* input channels */
33733a3f90c6SAndrew Thompson 	uint32_t ochs;			/* output channels */
33743a3f90c6SAndrew Thompson 	uint32_t c;
33753a3f90c6SAndrew Thompson 	uint32_t chs;			/* channels */
33763a3f90c6SAndrew Thompson 	uint32_t i;
33773a3f90c6SAndrew Thompson 	uint32_t o;
33783a3f90c6SAndrew Thompson 
33793a3f90c6SAndrew Thompson 	DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n",
33803a3f90c6SAndrew Thompson 	    d0->bUnitId, d0->bNrInPins);
33813a3f90c6SAndrew Thompson 
33823a3f90c6SAndrew Thompson 	/* compute the number of input channels */
33833a3f90c6SAndrew Thompson 
33843a3f90c6SAndrew Thompson 	ichs = 0;
33853a3f90c6SAndrew Thompson 	for (i = 0; i < d0->bNrInPins; i++) {
3386e2524b2eSHans Petter Selasky 		ichs += uaudio_mixer_get_cluster(
3387e2524b2eSHans Petter Selasky 		    d0->baSourceId[i], iot).bNrChannels;
33883a3f90c6SAndrew Thompson 	}
33893a3f90c6SAndrew Thompson 
33903a3f90c6SAndrew Thompson 	d1 = (const void *)(d0->baSourceId + d0->bNrInPins);
33913a3f90c6SAndrew Thompson 
33923a3f90c6SAndrew Thompson 	/* and the number of output channels */
33933a3f90c6SAndrew Thompson 
33943a3f90c6SAndrew Thompson 	ochs = d1->bNrChannels;
33953a3f90c6SAndrew Thompson 
33963a3f90c6SAndrew Thompson 	DPRINTFN(3, "ichs=%d ochs=%d\n", ichs, ochs);
33973a3f90c6SAndrew Thompson 
3398f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
33993a3f90c6SAndrew Thompson 
3400f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no);
3401f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_SIGNED_16;
34023a3f90c6SAndrew Thompson 
3403e2524b2eSHans Petter Selasky 	if (uaudio_mixer_verify_desc(d0, ((ichs * ochs) + 7) / 8) == NULL)
34043a3f90c6SAndrew Thompson 		return;
3405e2524b2eSHans Petter Selasky 
34063a3f90c6SAndrew Thompson 	for (p = i = 0; i < d0->bNrInPins; i++) {
3407e2524b2eSHans Petter Selasky 		chs = uaudio_mixer_get_cluster(
3408e2524b2eSHans Petter Selasky 		    d0->baSourceId[i], iot).bNrChannels;
34093a3f90c6SAndrew Thompson 		mc = 0;
34103a3f90c6SAndrew Thompson 		for (c = 0; c < chs; c++) {
34113a3f90c6SAndrew Thompson 			mo = 0;
34123a3f90c6SAndrew Thompson 			for (o = 0; o < ochs; o++) {
34133a3f90c6SAndrew Thompson 				bno = ((p + c) * ochs) + o;
3414e2524b2eSHans Petter Selasky 				if (BIT_TEST(d1->bmControls, bno))
34153a3f90c6SAndrew Thompson 					mo++;
34163a3f90c6SAndrew Thompson 			}
3417e2524b2eSHans Petter Selasky 			if (mo == 1)
34183a3f90c6SAndrew Thompson 				mc++;
34193a3f90c6SAndrew Thompson 		}
34203a3f90c6SAndrew Thompson 		if ((mc == chs) && (chs <= MIX_MAX_CHAN)) {
34213a3f90c6SAndrew Thompson 			/* repeat bit-scan */
34223a3f90c6SAndrew Thompson 
34233a3f90c6SAndrew Thompson 			mc = 0;
34243a3f90c6SAndrew Thompson 			for (c = 0; c < chs; c++) {
34253a3f90c6SAndrew Thompson 				for (o = 0; o < ochs; o++) {
34263a3f90c6SAndrew Thompson 					bno = ((p + c) * ochs) + o;
3427e2524b2eSHans Petter Selasky 					if (BIT_TEST(d1->bmControls, bno))
3428f7e62ad0SHans Petter Selasky 						MIX(sc).wValue[mc++] = MAKE_WORD(p + c + 1, o + 1);
34293a3f90c6SAndrew Thompson 				}
34303a3f90c6SAndrew Thompson 			}
3431f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = chs;
3432f7e62ad0SHans Petter Selasky 			uaudio_mixer_add_ctl(sc, &MIX(sc));
3433e2524b2eSHans Petter Selasky 		}
3434e2524b2eSHans Petter Selasky 		p += chs;
3435e2524b2eSHans Petter Selasky 	}
3436e2524b2eSHans Petter Selasky }
3437e2524b2eSHans Petter Selasky 
3438e2524b2eSHans Petter Selasky static void
uaudio20_mixer_add_mixer(struct uaudio_softc * sc,const struct uaudio_terminal_node * iot,int id)3439e2524b2eSHans Petter Selasky uaudio20_mixer_add_mixer(struct uaudio_softc *sc,
3440e2524b2eSHans Petter Selasky     const struct uaudio_terminal_node *iot, int id)
3441e2524b2eSHans Petter Selasky {
3442e2524b2eSHans Petter Selasky 	const struct usb_audio20_mixer_unit_0 *d0 = iot[id].u.mu_v2;
3443e2524b2eSHans Petter Selasky 	const struct usb_audio20_mixer_unit_1 *d1;
3444e2524b2eSHans Petter Selasky 
3445e2524b2eSHans Petter Selasky 	uint32_t bno;			/* bit number */
3446e2524b2eSHans Petter Selasky 	uint32_t p;			/* bit number accumulator */
3447e2524b2eSHans Petter Selasky 	uint32_t mo;			/* matching outputs */
3448e2524b2eSHans Petter Selasky 	uint32_t mc;			/* matching channels */
3449e2524b2eSHans Petter Selasky 	uint32_t ichs;			/* input channels */
3450e2524b2eSHans Petter Selasky 	uint32_t ochs;			/* output channels */
3451e2524b2eSHans Petter Selasky 	uint32_t c;
3452e2524b2eSHans Petter Selasky 	uint32_t chs;			/* channels */
3453e2524b2eSHans Petter Selasky 	uint32_t i;
3454e2524b2eSHans Petter Selasky 	uint32_t o;
3455e2524b2eSHans Petter Selasky 
3456e2524b2eSHans Petter Selasky 	DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n",
3457e2524b2eSHans Petter Selasky 	    d0->bUnitId, d0->bNrInPins);
3458e2524b2eSHans Petter Selasky 
3459e2524b2eSHans Petter Selasky 	/* compute the number of input channels */
3460e2524b2eSHans Petter Selasky 
3461e2524b2eSHans Petter Selasky 	ichs = 0;
3462e2524b2eSHans Petter Selasky 	for (i = 0; i < d0->bNrInPins; i++) {
3463e2524b2eSHans Petter Selasky 		ichs += uaudio20_mixer_get_cluster(
3464e2524b2eSHans Petter Selasky 		    d0->baSourceId[i], iot).bNrChannels;
3465e2524b2eSHans Petter Selasky 	}
3466e2524b2eSHans Petter Selasky 
3467e2524b2eSHans Petter Selasky 	d1 = (const void *)(d0->baSourceId + d0->bNrInPins);
3468e2524b2eSHans Petter Selasky 
3469e2524b2eSHans Petter Selasky 	/* and the number of output channels */
3470e2524b2eSHans Petter Selasky 
3471e2524b2eSHans Petter Selasky 	ochs = d1->bNrChannels;
3472e2524b2eSHans Petter Selasky 
3473e2524b2eSHans Petter Selasky 	DPRINTFN(3, "ichs=%d ochs=%d\n", ichs, ochs);
3474e2524b2eSHans Petter Selasky 
3475f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
3476e2524b2eSHans Petter Selasky 
3477f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no);
3478f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_SIGNED_16;
3479e2524b2eSHans Petter Selasky 
3480e2524b2eSHans Petter Selasky 	if (uaudio20_mixer_verify_desc(d0, ((ichs * ochs) + 7) / 8) == NULL)
3481e2524b2eSHans Petter Selasky 		return;
3482e2524b2eSHans Petter Selasky 
3483e2524b2eSHans Petter Selasky 	for (p = i = 0; i < d0->bNrInPins; i++) {
3484e2524b2eSHans Petter Selasky 		chs = uaudio20_mixer_get_cluster(
3485e2524b2eSHans Petter Selasky 		    d0->baSourceId[i], iot).bNrChannels;
3486e2524b2eSHans Petter Selasky 		mc = 0;
3487e2524b2eSHans Petter Selasky 		for (c = 0; c < chs; c++) {
3488e2524b2eSHans Petter Selasky 			mo = 0;
3489e2524b2eSHans Petter Selasky 			for (o = 0; o < ochs; o++) {
3490e2524b2eSHans Petter Selasky 				bno = ((p + c) * ochs) + o;
3491e2524b2eSHans Petter Selasky 				if (BIT_TEST(d1->bmControls, bno))
3492e2524b2eSHans Petter Selasky 					mo++;
3493e2524b2eSHans Petter Selasky 			}
3494e2524b2eSHans Petter Selasky 			if (mo == 1)
3495e2524b2eSHans Petter Selasky 				mc++;
3496e2524b2eSHans Petter Selasky 		}
3497e2524b2eSHans Petter Selasky 		if ((mc == chs) && (chs <= MIX_MAX_CHAN)) {
3498e2524b2eSHans Petter Selasky 			/* repeat bit-scan */
3499e2524b2eSHans Petter Selasky 
3500e2524b2eSHans Petter Selasky 			mc = 0;
3501e2524b2eSHans Petter Selasky 			for (c = 0; c < chs; c++) {
3502e2524b2eSHans Petter Selasky 				for (o = 0; o < ochs; o++) {
3503e2524b2eSHans Petter Selasky 					bno = ((p + c) * ochs) + o;
3504e2524b2eSHans Petter Selasky 					if (BIT_TEST(d1->bmControls, bno))
3505f7e62ad0SHans Petter Selasky 						MIX(sc).wValue[mc++] = MAKE_WORD(p + c + 1, o + 1);
3506e2524b2eSHans Petter Selasky 				}
3507e2524b2eSHans Petter Selasky 			}
3508f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = chs;
3509f7e62ad0SHans Petter Selasky 			uaudio_mixer_add_ctl(sc, &MIX(sc));
35103a3f90c6SAndrew Thompson 		}
35113a3f90c6SAndrew Thompson 		p += chs;
35123a3f90c6SAndrew Thompson 	}
35133a3f90c6SAndrew Thompson }
35143a3f90c6SAndrew Thompson 
35153a3f90c6SAndrew Thompson static void
uaudio_mixer_check_selectors(struct uaudio_softc * sc)3516c2bc9ff7SHans Petter Selasky uaudio_mixer_check_selectors(struct uaudio_softc *sc)
3517c2bc9ff7SHans Petter Selasky {
3518c2bc9ff7SHans Petter Selasky 	uint8_t reserve_feature[] = {
3519c2bc9ff7SHans Petter Selasky 	    SOUND_MIXER_LINE,
3520c2bc9ff7SHans Petter Selasky 	    SOUND_MIXER_LINE1,
3521c2bc9ff7SHans Petter Selasky 	    SOUND_MIXER_LINE2,
3522c2bc9ff7SHans Petter Selasky 	    SOUND_MIXER_LINE3,
3523c2bc9ff7SHans Petter Selasky 	    SOUND_MIXER_DIGITAL1,
3524c2bc9ff7SHans Petter Selasky 	    SOUND_MIXER_DIGITAL2,
3525c2bc9ff7SHans Petter Selasky 	    SOUND_MIXER_DIGITAL3,
3526c2bc9ff7SHans Petter Selasky 	};
3527c2bc9ff7SHans Petter Selasky 	const uint16_t reserve_max =
3528c2bc9ff7SHans Petter Selasky 	    sizeof(reserve_feature) / sizeof(reserve_feature[0]);
3529c2bc9ff7SHans Petter Selasky 	uint16_t i;
3530c2bc9ff7SHans Petter Selasky 	uint16_t j;
3531c2bc9ff7SHans Petter Selasky 	uint16_t k;
3532c2bc9ff7SHans Petter Selasky 
3533c2bc9ff7SHans Petter Selasky 	/* remove existing selector types from the reserve */
3534c2bc9ff7SHans Petter Selasky 	for (i = 0; i < MIX(sc).maxval; i++) {
3535c2bc9ff7SHans Petter Selasky 		if (MIX(sc).slctrtype[i] == SOUND_MIXER_NRDEVICES)
3536c2bc9ff7SHans Petter Selasky 			continue;
3537c2bc9ff7SHans Petter Selasky 		for (j = 0; j != reserve_max; j++) {
3538c2bc9ff7SHans Petter Selasky 			if (reserve_feature[j] == MIX(sc).slctrtype[i])
3539c2bc9ff7SHans Petter Selasky 				reserve_feature[j] = SOUND_MIXER_NRDEVICES;
3540c2bc9ff7SHans Petter Selasky 		}
3541c2bc9ff7SHans Petter Selasky 	}
3542c2bc9ff7SHans Petter Selasky 
3543c2bc9ff7SHans Petter Selasky 	/* make sure selector types are not overlapping */
3544c2bc9ff7SHans Petter Selasky 	for (i = 0; i < MIX(sc).maxval; i++) {
3545c2bc9ff7SHans Petter Selasky 		if (MIX(sc).slctrtype[i] == SOUND_MIXER_NRDEVICES)
3546c2bc9ff7SHans Petter Selasky 			continue;
3547c2bc9ff7SHans Petter Selasky 		for (j = i + 1; j < MIX(sc).maxval; j++) {
3548c2bc9ff7SHans Petter Selasky 			if (MIX(sc).slctrtype[j] == SOUND_MIXER_NRDEVICES)
3549c2bc9ff7SHans Petter Selasky 				continue;
3550c2bc9ff7SHans Petter Selasky 			if (MIX(sc).slctrtype[i] != MIX(sc).slctrtype[j])
3551c2bc9ff7SHans Petter Selasky 				continue;
3552c2bc9ff7SHans Petter Selasky 			for (k = 0; k != reserve_max; k++) {
3553c2bc9ff7SHans Petter Selasky 				if (reserve_feature[k] == SOUND_MIXER_NRDEVICES)
3554c2bc9ff7SHans Petter Selasky 					continue;
3555c2bc9ff7SHans Petter Selasky 				MIX(sc).slctrtype[j] = reserve_feature[k];
3556c2bc9ff7SHans Petter Selasky 				reserve_feature[k] = SOUND_MIXER_NRDEVICES;
3557c2bc9ff7SHans Petter Selasky 				break;
3558c2bc9ff7SHans Petter Selasky 			}
3559c2bc9ff7SHans Petter Selasky 			if (k == reserve_max) {
3560c2bc9ff7SHans Petter Selasky 				DPRINTF("Selector type %d is not selectable!\n", j);
3561c2bc9ff7SHans Petter Selasky 				MIX(sc).slctrtype[j] = SOUND_MIXER_NRDEVICES;
3562c2bc9ff7SHans Petter Selasky 			}
3563c2bc9ff7SHans Petter Selasky 		}
3564c2bc9ff7SHans Petter Selasky 	}
3565c2bc9ff7SHans Petter Selasky }
3566c2bc9ff7SHans Petter Selasky 
3567c2bc9ff7SHans Petter Selasky static void
uaudio_mixer_add_selector(struct uaudio_softc * sc,const struct uaudio_terminal_node * iot,int id)35683a3f90c6SAndrew Thompson uaudio_mixer_add_selector(struct uaudio_softc *sc,
35693a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
35703a3f90c6SAndrew Thompson {
3571e2524b2eSHans Petter Selasky 	const struct usb_audio_selector_unit *d = iot[id].u.su_v1;
35723a3f90c6SAndrew Thompson 	uint16_t i;
35733a3f90c6SAndrew Thompson 
35743a3f90c6SAndrew Thompson 	DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n",
35753a3f90c6SAndrew Thompson 	    d->bUnitId, d->bNrInPins);
35763a3f90c6SAndrew Thompson 
3577902514f6SHans Petter Selasky 	if (d->bNrInPins == 0)
35783a3f90c6SAndrew Thompson 		return;
3579902514f6SHans Petter Selasky 
3580f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
35813a3f90c6SAndrew Thompson 
3582f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no);
3583f7e62ad0SHans Petter Selasky 	MIX(sc).wValue[0] = MAKE_WORD(0, 0);
3584f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
3585f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_SELECTOR;
3586f7e62ad0SHans Petter Selasky 	MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
3587f7e62ad0SHans Petter Selasky 	MIX(sc).minval = 1;
3588f7e62ad0SHans Petter Selasky 	MIX(sc).maxval = d->bNrInPins;
3589f7e62ad0SHans Petter Selasky 	MIX(sc).name = "selector";
3590902514f6SHans Petter Selasky 
3591902514f6SHans Petter Selasky 	i = d->baSourceId[d->bNrInPins];
3592902514f6SHans Petter Selasky 	if (i == 0 ||
3593902514f6SHans Petter Selasky 	    usbd_req_get_string_any(sc->sc_udev, NULL,
3594f7e62ad0SHans Petter Selasky 	    MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) {
3595f7e62ad0SHans Petter Selasky 		MIX(sc).desc[0] = 0;
3596902514f6SHans Petter Selasky 	}
35973a3f90c6SAndrew Thompson 
3598c2bc9ff7SHans Petter Selasky 	if (MIX(sc).maxval > MAX_SELECTOR_INPUT_PIN)
3599f7e62ad0SHans Petter Selasky 		MIX(sc).maxval = MAX_SELECTOR_INPUT_PIN;
3600c2bc9ff7SHans Petter Selasky 
3601c2bc9ff7SHans Petter Selasky 	MIX(sc).mul = MIX(sc).maxval - MIX(sc).minval;
36023a3f90c6SAndrew Thompson 
3603f7e62ad0SHans Petter Selasky 	for (i = 0; i < MIX(sc).maxval; i++) {
3604c2bc9ff7SHans Petter Selasky 		MIX(sc).slctrtype[i] =
3605c2bc9ff7SHans Petter Selasky 		    uaudio_mixer_determine_class(&iot[d->baSourceId[i]]);
3606e2524b2eSHans Petter Selasky 	}
3607c2bc9ff7SHans Petter Selasky 	for (; i < MAX_SELECTOR_INPUT_PIN; i++)
3608c2bc9ff7SHans Petter Selasky 		MIX(sc).slctrtype[i] = SOUND_MIXER_NRDEVICES;
3609e2524b2eSHans Petter Selasky 
3610c2bc9ff7SHans Petter Selasky 	uaudio_mixer_check_selectors(sc);
3611f7e62ad0SHans Petter Selasky 	uaudio_mixer_add_ctl(sc, &MIX(sc));
3612e2524b2eSHans Petter Selasky }
3613e2524b2eSHans Petter Selasky 
3614e2524b2eSHans Petter Selasky static void
uaudio20_mixer_add_selector(struct uaudio_softc * sc,const struct uaudio_terminal_node * iot,int id)3615e2524b2eSHans Petter Selasky uaudio20_mixer_add_selector(struct uaudio_softc *sc,
3616e2524b2eSHans Petter Selasky     const struct uaudio_terminal_node *iot, int id)
3617e2524b2eSHans Petter Selasky {
3618e2524b2eSHans Petter Selasky 	const struct usb_audio20_selector_unit *d = iot[id].u.su_v2;
3619e2524b2eSHans Petter Selasky 	uint16_t i;
3620e2524b2eSHans Petter Selasky 
3621e2524b2eSHans Petter Selasky 	DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n",
3622e2524b2eSHans Petter Selasky 	    d->bUnitId, d->bNrInPins);
3623e2524b2eSHans Petter Selasky 
3624e2524b2eSHans Petter Selasky 	if (d->bNrInPins == 0)
3625e2524b2eSHans Petter Selasky 		return;
3626e2524b2eSHans Petter Selasky 
3627f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
3628e2524b2eSHans Petter Selasky 
3629f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no);
3630f7e62ad0SHans Petter Selasky 	MIX(sc).wValue[0] = MAKE_WORD(0, 0);
3631f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
3632f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_SELECTOR;
3633f7e62ad0SHans Petter Selasky 	MIX(sc).ctl = SOUND_MIXER_NRDEVICES;
3634f7e62ad0SHans Petter Selasky 	MIX(sc).minval = 1;
3635f7e62ad0SHans Petter Selasky 	MIX(sc).maxval = d->bNrInPins;
3636f7e62ad0SHans Petter Selasky 	MIX(sc).name = "selector";
3637902514f6SHans Petter Selasky 
3638902514f6SHans Petter Selasky 	i = d->baSourceId[d->bNrInPins];
3639902514f6SHans Petter Selasky 	if (i == 0 ||
3640902514f6SHans Petter Selasky 	    usbd_req_get_string_any(sc->sc_udev, NULL,
3641f7e62ad0SHans Petter Selasky 	    MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) {
3642f7e62ad0SHans Petter Selasky 		MIX(sc).desc[0] = 0;
3643902514f6SHans Petter Selasky 	}
3644e2524b2eSHans Petter Selasky 
3645f7e62ad0SHans Petter Selasky 	if (MIX(sc).maxval > MAX_SELECTOR_INPUT_PIN)
3646f7e62ad0SHans Petter Selasky 		MIX(sc).maxval = MAX_SELECTOR_INPUT_PIN;
3647e2524b2eSHans Petter Selasky 
3648c2bc9ff7SHans Petter Selasky 	MIX(sc).mul = MIX(sc).maxval - MIX(sc).minval;
3649e2524b2eSHans Petter Selasky 
3650f7e62ad0SHans Petter Selasky 	for (i = 0; i < MIX(sc).maxval; i++) {
3651c2bc9ff7SHans Petter Selasky 		MIX(sc).slctrtype[i] =
3652c2bc9ff7SHans Petter Selasky 		    uaudio20_mixer_determine_class(&iot[d->baSourceId[i]]);
36533a3f90c6SAndrew Thompson 	}
3654c2bc9ff7SHans Petter Selasky 	for (; i < MAX_SELECTOR_INPUT_PIN; i++)
3655c2bc9ff7SHans Petter Selasky 		MIX(sc).slctrtype[i] = SOUND_MIXER_NRDEVICES;
36563a3f90c6SAndrew Thompson 
3657c2bc9ff7SHans Petter Selasky 	uaudio_mixer_check_selectors(sc);
3658f7e62ad0SHans Petter Selasky 	uaudio_mixer_add_ctl(sc, &MIX(sc));
36593a3f90c6SAndrew Thompson }
36603a3f90c6SAndrew Thompson 
36613a3f90c6SAndrew Thompson static uint32_t
uaudio_mixer_feature_get_bmaControls(const struct usb_audio_feature_unit * d,uint8_t i)36624c21be9bSRebecca Cran uaudio_mixer_feature_get_bmaControls(const struct usb_audio_feature_unit *d,
36636d917491SHans Petter Selasky     uint8_t i)
36643a3f90c6SAndrew Thompson {
36653a3f90c6SAndrew Thompson 	uint32_t temp = 0;
36666d917491SHans Petter Selasky 	uint32_t offset = (i * d->bControlSize);
36673a3f90c6SAndrew Thompson 
36683a3f90c6SAndrew Thompson 	if (d->bControlSize > 0) {
36693a3f90c6SAndrew Thompson 		temp |= d->bmaControls[offset];
36703a3f90c6SAndrew Thompson 		if (d->bControlSize > 1) {
36713a3f90c6SAndrew Thompson 			temp |= d->bmaControls[offset + 1] << 8;
36723a3f90c6SAndrew Thompson 			if (d->bControlSize > 2) {
36733a3f90c6SAndrew Thompson 				temp |= d->bmaControls[offset + 2] << 16;
36743a3f90c6SAndrew Thompson 				if (d->bControlSize > 3) {
36753a3f90c6SAndrew Thompson 					temp |= d->bmaControls[offset + 3] << 24;
36763a3f90c6SAndrew Thompson 				}
36773a3f90c6SAndrew Thompson 			}
36783a3f90c6SAndrew Thompson 		}
36793a3f90c6SAndrew Thompson 	}
36803a3f90c6SAndrew Thompson 	return (temp);
36813a3f90c6SAndrew Thompson }
36823a3f90c6SAndrew Thompson 
36833a3f90c6SAndrew Thompson static void
uaudio_mixer_add_feature(struct uaudio_softc * sc,const struct uaudio_terminal_node * iot,int id)36843a3f90c6SAndrew Thompson uaudio_mixer_add_feature(struct uaudio_softc *sc,
36853a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
36863a3f90c6SAndrew Thompson {
3687e2524b2eSHans Petter Selasky 	const struct usb_audio_feature_unit *d = iot[id].u.fu_v1;
36883a3f90c6SAndrew Thompson 	uint32_t fumask;
36893a3f90c6SAndrew Thompson 	uint32_t mmask;
36903a3f90c6SAndrew Thompson 	uint32_t cmask;
36913a3f90c6SAndrew Thompson 	uint16_t mixernumber;
36923a3f90c6SAndrew Thompson 	uint8_t nchan;
36933a3f90c6SAndrew Thompson 	uint8_t chan;
36943a3f90c6SAndrew Thompson 	uint8_t ctl;
36953a3f90c6SAndrew Thompson 	uint8_t i;
36963a3f90c6SAndrew Thompson 
3697902514f6SHans Petter Selasky 	if (d->bControlSize == 0)
36983a3f90c6SAndrew Thompson 		return;
3699902514f6SHans Petter Selasky 
3700f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
37013a3f90c6SAndrew Thompson 
37023a3f90c6SAndrew Thompson 	nchan = (d->bLength - 7) / d->bControlSize;
37033a3f90c6SAndrew Thompson 	mmask = uaudio_mixer_feature_get_bmaControls(d, 0);
37043a3f90c6SAndrew Thompson 	cmask = 0;
37053a3f90c6SAndrew Thompson 
3706902514f6SHans Petter Selasky 	if (nchan == 0)
37073a3f90c6SAndrew Thompson 		return;
3708902514f6SHans Petter Selasky 
37093a3f90c6SAndrew Thompson 	/* figure out what we can control */
37103a3f90c6SAndrew Thompson 
37113a3f90c6SAndrew Thompson 	for (chan = 1; chan < nchan; chan++) {
37123a3f90c6SAndrew Thompson 		DPRINTFN(10, "chan=%d mask=%x\n",
37133a3f90c6SAndrew Thompson 		    chan, uaudio_mixer_feature_get_bmaControls(d, chan));
37143a3f90c6SAndrew Thompson 
37153a3f90c6SAndrew Thompson 		cmask |= uaudio_mixer_feature_get_bmaControls(d, chan);
37163a3f90c6SAndrew Thompson 	}
37173a3f90c6SAndrew Thompson 
3718f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no);
37193a3f90c6SAndrew Thompson 
372011f09b17SHans Petter Selasky 	i = d->bmaControls[nchan * d->bControlSize];
3721902514f6SHans Petter Selasky 	if (i == 0 ||
3722902514f6SHans Petter Selasky 	    usbd_req_get_string_any(sc->sc_udev, NULL,
3723f7e62ad0SHans Petter Selasky 	    MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) {
3724f7e62ad0SHans Petter Selasky 		MIX(sc).desc[0] = 0;
3725902514f6SHans Petter Selasky 	}
3726902514f6SHans Petter Selasky 
372711f09b17SHans Petter Selasky 	if (nchan > MIX_MAX_CHAN)
372811f09b17SHans Petter Selasky 		nchan = MIX_MAX_CHAN;
372911f09b17SHans Petter Selasky 
37303a3f90c6SAndrew Thompson 	for (ctl = 1; ctl <= LOUDNESS_CONTROL; ctl++) {
37313a3f90c6SAndrew Thompson 		fumask = FU_MASK(ctl);
37323a3f90c6SAndrew Thompson 
37333a3f90c6SAndrew Thompson 		DPRINTFN(5, "ctl=%d fumask=0x%04x\n",
37343a3f90c6SAndrew Thompson 		    ctl, fumask);
37353a3f90c6SAndrew Thompson 
37363a3f90c6SAndrew Thompson 		if (mmask & fumask) {
3737f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = 1;
3738f7e62ad0SHans Petter Selasky 			MIX(sc).wValue[0] = MAKE_WORD(ctl, 0);
37393a3f90c6SAndrew Thompson 		} else if (cmask & fumask) {
3740f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = nchan - 1;
37413a3f90c6SAndrew Thompson 			for (i = 1; i < nchan; i++) {
37423a3f90c6SAndrew Thompson 				if (uaudio_mixer_feature_get_bmaControls(d, i) & fumask)
3743f7e62ad0SHans Petter Selasky 					MIX(sc).wValue[i - 1] = MAKE_WORD(ctl, i);
37443a3f90c6SAndrew Thompson 				else
3745f7e62ad0SHans Petter Selasky 					MIX(sc).wValue[i - 1] = -1;
37463a3f90c6SAndrew Thompson 			}
37473a3f90c6SAndrew Thompson 		} else {
37483a3f90c6SAndrew Thompson 			continue;
37493a3f90c6SAndrew Thompson 		}
37503a3f90c6SAndrew Thompson 
3751c2bc9ff7SHans Petter Selasky 		mixernumber = uaudio_mixer_determine_class(&iot[id]);
37523a3f90c6SAndrew Thompson 
37533a3f90c6SAndrew Thompson 		switch (ctl) {
37543a3f90c6SAndrew Thompson 		case MUTE_CONTROL:
3755f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
375691feacd1SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_MUTE;
3757f7e62ad0SHans Petter Selasky 			MIX(sc).name = "mute";
37583a3f90c6SAndrew Thompson 			break;
37593a3f90c6SAndrew Thompson 
37603a3f90c6SAndrew Thompson 		case VOLUME_CONTROL:
3761f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_16;
3762f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = mixernumber;
3763f7e62ad0SHans Petter Selasky 			MIX(sc).name = "vol";
37643a3f90c6SAndrew Thompson 			break;
37653a3f90c6SAndrew Thompson 
37663a3f90c6SAndrew Thompson 		case BASS_CONTROL:
3767f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_8;
3768f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_BASS;
3769f7e62ad0SHans Petter Selasky 			MIX(sc).name = "bass";
37703a3f90c6SAndrew Thompson 			break;
37713a3f90c6SAndrew Thompson 
37723a3f90c6SAndrew Thompson 		case MID_CONTROL:
3773f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_8;
3774f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3775f7e62ad0SHans Petter Selasky 			MIX(sc).name = "mid";
37763a3f90c6SAndrew Thompson 			break;
37773a3f90c6SAndrew Thompson 
37783a3f90c6SAndrew Thompson 		case TREBLE_CONTROL:
3779f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_8;
3780f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_TREBLE;
3781f7e62ad0SHans Petter Selasky 			MIX(sc).name = "treble";
37823a3f90c6SAndrew Thompson 			break;
37833a3f90c6SAndrew Thompson 
37843a3f90c6SAndrew Thompson 		case GRAPHIC_EQUALIZER_CONTROL:
37853a3f90c6SAndrew Thompson 			continue;	/* XXX don't add anything */
37863a3f90c6SAndrew Thompson 
37873a3f90c6SAndrew Thompson 		case AGC_CONTROL:
3788f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
3789f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3790f7e62ad0SHans Petter Selasky 			MIX(sc).name = "agc";
37913a3f90c6SAndrew Thompson 			break;
37923a3f90c6SAndrew Thompson 
37933a3f90c6SAndrew Thompson 		case DELAY_CONTROL:
3794f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_UNSIGNED_16;
3795f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3796f7e62ad0SHans Petter Selasky 			MIX(sc).name = "delay";
37973a3f90c6SAndrew Thompson 			break;
37983a3f90c6SAndrew Thompson 
37993a3f90c6SAndrew Thompson 		case BASS_BOOST_CONTROL:
3800f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
3801f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3802f7e62ad0SHans Petter Selasky 			MIX(sc).name = "boost";
38033a3f90c6SAndrew Thompson 			break;
38043a3f90c6SAndrew Thompson 
38053a3f90c6SAndrew Thompson 		case LOUDNESS_CONTROL:
3806f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
3807f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_LOUD;	/* Is this correct ? */
3808f7e62ad0SHans Petter Selasky 			MIX(sc).name = "loudness";
38093a3f90c6SAndrew Thompson 			break;
38103a3f90c6SAndrew Thompson 
38113a3f90c6SAndrew Thompson 		default:
3812f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_UNKNOWN;
38133a3f90c6SAndrew Thompson 			break;
38143a3f90c6SAndrew Thompson 		}
38153a3f90c6SAndrew Thompson 
3816f7e62ad0SHans Petter Selasky 		if (MIX(sc).type != MIX_UNKNOWN)
3817f7e62ad0SHans Petter Selasky 			uaudio_mixer_add_ctl(sc, &MIX(sc));
38183a3f90c6SAndrew Thompson 	}
38193a3f90c6SAndrew Thompson }
3820e2524b2eSHans Petter Selasky 
3821e2524b2eSHans Petter Selasky static void
uaudio20_mixer_add_feature(struct uaudio_softc * sc,const struct uaudio_terminal_node * iot,int id)3822e2524b2eSHans Petter Selasky uaudio20_mixer_add_feature(struct uaudio_softc *sc,
3823e2524b2eSHans Petter Selasky     const struct uaudio_terminal_node *iot, int id)
3824e2524b2eSHans Petter Selasky {
3825e2524b2eSHans Petter Selasky 	const struct usb_audio20_feature_unit *d = iot[id].u.fu_v2;
3826e2524b2eSHans Petter Selasky 	uint32_t ctl;
3827e2524b2eSHans Petter Selasky 	uint32_t mmask;
3828e2524b2eSHans Petter Selasky 	uint32_t cmask;
3829e2524b2eSHans Petter Selasky 	uint16_t mixernumber;
3830e2524b2eSHans Petter Selasky 	uint8_t nchan;
3831e2524b2eSHans Petter Selasky 	uint8_t chan;
3832e2524b2eSHans Petter Selasky 	uint8_t i;
3833e2524b2eSHans Petter Selasky 	uint8_t what;
3834e2524b2eSHans Petter Selasky 
3835e2524b2eSHans Petter Selasky 	if (UGETDW(d->bmaControls[0]) == 0)
3836e2524b2eSHans Petter Selasky 		return;
3837e2524b2eSHans Petter Selasky 
3838f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
3839e2524b2eSHans Petter Selasky 
3840e2524b2eSHans Petter Selasky 	nchan = (d->bLength - 6) / 4;
3841e2524b2eSHans Petter Selasky 	mmask = UGETDW(d->bmaControls[0]);
3842e2524b2eSHans Petter Selasky 	cmask = 0;
3843e2524b2eSHans Petter Selasky 
3844e2524b2eSHans Petter Selasky 	if (nchan == 0)
3845e2524b2eSHans Petter Selasky 		return;
3846e2524b2eSHans Petter Selasky 
3847e2524b2eSHans Petter Selasky 	/* figure out what we can control */
3848e2524b2eSHans Petter Selasky 
3849e2524b2eSHans Petter Selasky 	for (chan = 1; chan < nchan; chan++)
3850e2524b2eSHans Petter Selasky 		cmask |= UGETDW(d->bmaControls[chan]);
3851e2524b2eSHans Petter Selasky 
3852f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(d->bUnitId, sc->sc_mixer_iface_no);
3853e2524b2eSHans Petter Selasky 
3854902514f6SHans Petter Selasky 	i = d->bmaControls[nchan][0];
3855902514f6SHans Petter Selasky 	if (i == 0 ||
3856902514f6SHans Petter Selasky 	    usbd_req_get_string_any(sc->sc_udev, NULL,
3857f7e62ad0SHans Petter Selasky 	    MIX(sc).desc, sizeof(MIX(sc).desc), i) != 0) {
3858f7e62ad0SHans Petter Selasky 		MIX(sc).desc[0] = 0;
3859902514f6SHans Petter Selasky 	}
3860902514f6SHans Petter Selasky 
386111f09b17SHans Petter Selasky 	if (nchan > MIX_MAX_CHAN)
386211f09b17SHans Petter Selasky 		nchan = MIX_MAX_CHAN;
386311f09b17SHans Petter Selasky 
3864e2524b2eSHans Petter Selasky 	for (ctl = 3; ctl != 0; ctl <<= 2) {
3865c2bc9ff7SHans Petter Selasky 		mixernumber = uaudio20_mixer_determine_class(&iot[id]);
3866e2524b2eSHans Petter Selasky 
3867e2524b2eSHans Petter Selasky 		switch (ctl) {
3868e2524b2eSHans Petter Selasky 		case (3 << 0):
3869f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
387091feacd1SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_MUTE;
3871f7e62ad0SHans Petter Selasky 			MIX(sc).name = "mute";
3872e2524b2eSHans Petter Selasky 			what = MUTE_CONTROL;
3873e2524b2eSHans Petter Selasky 			break;
3874e2524b2eSHans Petter Selasky 		case (3 << 2):
3875f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_16;
3876f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = mixernumber;
3877f7e62ad0SHans Petter Selasky 			MIX(sc).name = "vol";
3878e2524b2eSHans Petter Selasky 			what = VOLUME_CONTROL;
3879e2524b2eSHans Petter Selasky 			break;
3880e2524b2eSHans Petter Selasky 		case (3 << 4):
3881f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_8;
3882f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_BASS;
3883f7e62ad0SHans Petter Selasky 			MIX(sc).name = "bass";
3884e2524b2eSHans Petter Selasky 			what = BASS_CONTROL;
3885e2524b2eSHans Petter Selasky 			break;
3886e2524b2eSHans Petter Selasky 		case (3 << 6):
3887f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_8;
3888f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3889f7e62ad0SHans Petter Selasky 			MIX(sc).name = "mid";
3890e2524b2eSHans Petter Selasky 			what = MID_CONTROL;
3891e2524b2eSHans Petter Selasky 			break;
3892e2524b2eSHans Petter Selasky 		case (3 << 8):
3893f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_8;
3894f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_TREBLE;
3895f7e62ad0SHans Petter Selasky 			MIX(sc).name = "treble";
3896e2524b2eSHans Petter Selasky 			what = TREBLE_CONTROL;
3897e2524b2eSHans Petter Selasky 			break;
3898e2524b2eSHans Petter Selasky 		case (3 << 12):
3899f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
3900f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3901f7e62ad0SHans Petter Selasky 			MIX(sc).name = "agc";
3902e2524b2eSHans Petter Selasky 			what = AGC_CONTROL;
3903e2524b2eSHans Petter Selasky 			break;
3904e2524b2eSHans Petter Selasky 		case (3 << 14):
3905f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_UNSIGNED_16;
3906f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3907f7e62ad0SHans Petter Selasky 			MIX(sc).name = "delay";
3908e2524b2eSHans Petter Selasky 			what = DELAY_CONTROL;
3909e2524b2eSHans Petter Selasky 			break;
3910e2524b2eSHans Petter Selasky 		case (3 << 16):
3911f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
3912f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_NRDEVICES;	/* XXXXX */
3913f7e62ad0SHans Petter Selasky 			MIX(sc).name = "boost";
3914e2524b2eSHans Petter Selasky 			what = BASS_BOOST_CONTROL;
3915e2524b2eSHans Petter Selasky 			break;
3916e2524b2eSHans Petter Selasky 		case (3 << 18):
3917f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_ON_OFF;
3918f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = SOUND_MIXER_LOUD;	/* Is this correct ? */
3919f7e62ad0SHans Petter Selasky 			MIX(sc).name = "loudness";
3920e2524b2eSHans Petter Selasky 			what = LOUDNESS_CONTROL;
3921e2524b2eSHans Petter Selasky 			break;
3922e2524b2eSHans Petter Selasky 		case (3 << 20):
3923f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_16;
3924f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = mixernumber;
3925f7e62ad0SHans Petter Selasky 			MIX(sc).name = "igain";
3926e2524b2eSHans Petter Selasky 			what = INPUT_GAIN_CONTROL;
3927e2524b2eSHans Petter Selasky 			break;
3928e2524b2eSHans Petter Selasky 		case (3 << 22):
3929f7e62ad0SHans Petter Selasky 			MIX(sc).type = MIX_SIGNED_16;
3930f7e62ad0SHans Petter Selasky 			MIX(sc).ctl = mixernumber;
3931f7e62ad0SHans Petter Selasky 			MIX(sc).name = "igainpad";
3932e2524b2eSHans Petter Selasky 			what = INPUT_GAIN_PAD_CONTROL;
3933e2524b2eSHans Petter Selasky 			break;
3934e2524b2eSHans Petter Selasky 		default:
3935e2524b2eSHans Petter Selasky 			continue;
3936e2524b2eSHans Petter Selasky 		}
3937e2524b2eSHans Petter Selasky 
3938e2524b2eSHans Petter Selasky 		if ((mmask & ctl) == ctl) {
3939f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = 1;
3940f7e62ad0SHans Petter Selasky 			MIX(sc).wValue[0] = MAKE_WORD(what, 0);
3941e2524b2eSHans Petter Selasky 		} else if ((cmask & ctl) == ctl) {
3942f7e62ad0SHans Petter Selasky 			MIX(sc).nchan = nchan - 1;
3943e2524b2eSHans Petter Selasky 			for (i = 1; i < nchan; i++) {
3944e2524b2eSHans Petter Selasky 				if ((UGETDW(d->bmaControls[i]) & ctl) == ctl)
3945f7e62ad0SHans Petter Selasky 					MIX(sc).wValue[i - 1] = MAKE_WORD(what, i);
3946e2524b2eSHans Petter Selasky 				else
3947f7e62ad0SHans Petter Selasky 					MIX(sc).wValue[i - 1] = -1;
3948e2524b2eSHans Petter Selasky 			}
3949e2524b2eSHans Petter Selasky 		} else {
3950e2524b2eSHans Petter Selasky 			continue;
3951e2524b2eSHans Petter Selasky 		}
3952e2524b2eSHans Petter Selasky 
3953f7e62ad0SHans Petter Selasky 		if (MIX(sc).type != MIX_UNKNOWN)
3954f7e62ad0SHans Petter Selasky 			uaudio_mixer_add_ctl(sc, &MIX(sc));
3955e2524b2eSHans Petter Selasky 	}
39563a3f90c6SAndrew Thompson }
39573a3f90c6SAndrew Thompson 
39583a3f90c6SAndrew Thompson static void
uaudio_mixer_add_processing_updown(struct uaudio_softc * sc,const struct uaudio_terminal_node * iot,int id)39593a3f90c6SAndrew Thompson uaudio_mixer_add_processing_updown(struct uaudio_softc *sc,
39603a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
39613a3f90c6SAndrew Thompson {
3962e2524b2eSHans Petter Selasky 	const struct usb_audio_processing_unit_0 *d0 = iot[id].u.pu_v1;
39634c21be9bSRebecca Cran 	const struct usb_audio_processing_unit_1 *d1 =
39643a3f90c6SAndrew Thompson 	    (const void *)(d0->baSourceId + d0->bNrInPins);
39654c21be9bSRebecca Cran 	const struct usb_audio_processing_unit_updown *ud =
39663a3f90c6SAndrew Thompson 	    (const void *)(d1->bmControls + d1->bControlSize);
39673a3f90c6SAndrew Thompson 	uint8_t i;
39683a3f90c6SAndrew Thompson 
39693a3f90c6SAndrew Thompson 	if (uaudio_mixer_verify_desc(d0, sizeof(*ud)) == NULL) {
39703a3f90c6SAndrew Thompson 		return;
39713a3f90c6SAndrew Thompson 	}
39723a3f90c6SAndrew Thompson 	if (uaudio_mixer_verify_desc(d0, sizeof(*ud) + (2 * ud->bNrModes))
39733a3f90c6SAndrew Thompson 	    == NULL) {
39743a3f90c6SAndrew Thompson 		return;
39753a3f90c6SAndrew Thompson 	}
39763a3f90c6SAndrew Thompson 	DPRINTFN(3, "bUnitId=%d bNrModes=%d\n",
39773a3f90c6SAndrew Thompson 	    d0->bUnitId, ud->bNrModes);
39783a3f90c6SAndrew Thompson 
39793a3f90c6SAndrew Thompson 	if (!(d1->bmControls[0] & UA_PROC_MASK(UD_MODE_SELECT_CONTROL))) {
39803a3f90c6SAndrew Thompson 		DPRINTF("no mode select\n");
39813a3f90c6SAndrew Thompson 		return;
39823a3f90c6SAndrew Thompson 	}
3983f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
39843a3f90c6SAndrew Thompson 
3985f7e62ad0SHans Petter Selasky 	MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no);
3986f7e62ad0SHans Petter Selasky 	MIX(sc).nchan = 1;
3987f7e62ad0SHans Petter Selasky 	MIX(sc).wValue[0] = MAKE_WORD(UD_MODE_SELECT_CONTROL, 0);
3988f7e62ad0SHans Petter Selasky 	MIX(sc).type = MIX_ON_OFF;		/* XXX */
39893a3f90c6SAndrew Thompson 
39903a3f90c6SAndrew Thompson 	for (i = 0; i < ud->bNrModes; i++) {
39913a3f90c6SAndrew Thompson 		DPRINTFN(3, "i=%d bm=0x%x\n", i, UGETW(ud->waModes[i]));
39923a3f90c6SAndrew Thompson 		/* XXX */
39933a3f90c6SAndrew Thompson 	}
39943a3f90c6SAndrew Thompson 
3995f7e62ad0SHans Petter Selasky 	uaudio_mixer_add_ctl(sc, &MIX(sc));
39963a3f90c6SAndrew Thompson }
39973a3f90c6SAndrew Thompson 
39983a3f90c6SAndrew Thompson static void
uaudio_mixer_add_processing(struct uaudio_softc * sc,const struct uaudio_terminal_node * iot,int id)39993a3f90c6SAndrew Thompson uaudio_mixer_add_processing(struct uaudio_softc *sc,
40003a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
40013a3f90c6SAndrew Thompson {
4002e2524b2eSHans Petter Selasky 	const struct usb_audio_processing_unit_0 *d0 = iot[id].u.pu_v1;
40034c21be9bSRebecca Cran 	const struct usb_audio_processing_unit_1 *d1 =
40043a3f90c6SAndrew Thompson 	    (const void *)(d0->baSourceId + d0->bNrInPins);
40053a3f90c6SAndrew Thompson 	uint16_t ptype;
40063a3f90c6SAndrew Thompson 
4007f7e62ad0SHans Petter Selasky 	memset(&MIX(sc), 0, sizeof(MIX(sc)));
40083a3f90c6SAndrew Thompson 
40093a3f90c6SAndrew Thompson 	ptype = UGETW(d0->wProcessType);
40103a3f90c6SAndrew Thompson 
40113a3f90c6SAndrew Thompson 	DPRINTFN(3, "wProcessType=%d bUnitId=%d "
40123a3f90c6SAndrew Thompson 	    "bNrInPins=%d\n", ptype, d0->bUnitId, d0->bNrInPins);
40133a3f90c6SAndrew Thompson 
40143a3f90c6SAndrew Thompson 	if (d1->bControlSize == 0) {
40153a3f90c6SAndrew Thompson 		return;
40163a3f90c6SAndrew Thompson 	}
40173a3f90c6SAndrew Thompson 	if (d1->bmControls[0] & UA_PROC_ENABLE_MASK) {
4018f7e62ad0SHans Petter Selasky 		MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no);
4019f7e62ad0SHans Petter Selasky 		MIX(sc).nchan = 1;
4020f7e62ad0SHans Petter Selasky 		MIX(sc).wValue[0] = MAKE_WORD(XX_ENABLE_CONTROL, 0);
4021f7e62ad0SHans Petter Selasky 		MIX(sc).type = MIX_ON_OFF;
4022f7e62ad0SHans Petter Selasky 		uaudio_mixer_add_ctl(sc, &MIX(sc));
40233a3f90c6SAndrew Thompson 	}
40243a3f90c6SAndrew Thompson 	switch (ptype) {
40253a3f90c6SAndrew Thompson 	case UPDOWNMIX_PROCESS:
40263a3f90c6SAndrew Thompson 		uaudio_mixer_add_processing_updown(sc, iot, id);
40273a3f90c6SAndrew Thompson 		break;
40283a3f90c6SAndrew Thompson 
40293a3f90c6SAndrew Thompson 	case DOLBY_PROLOGIC_PROCESS:
40303a3f90c6SAndrew Thompson 	case P3D_STEREO_EXTENDER_PROCESS:
40313a3f90c6SAndrew Thompson 	case REVERBATION_PROCESS:
40323a3f90c6SAndrew Thompson 	case CHORUS_PROCESS:
40333a3f90c6SAndrew Thompson 	case DYN_RANGE_COMP_PROCESS:
40343a3f90c6SAndrew Thompson 	default:
40353a3f90c6SAndrew Thompson 		DPRINTF("unit %d, type=%d is not implemented\n",
40363a3f90c6SAndrew Thompson 		    d0->bUnitId, ptype);
40373a3f90c6SAndrew Thompson 		break;
40383a3f90c6SAndrew Thompson 	}
40393a3f90c6SAndrew Thompson }
40403a3f90c6SAndrew Thompson 
40413a3f90c6SAndrew Thompson static void
uaudio_mixer_add_extension(struct uaudio_softc * sc,const struct uaudio_terminal_node * iot,int id)40423a3f90c6SAndrew Thompson uaudio_mixer_add_extension(struct uaudio_softc *sc,
40433a3f90c6SAndrew Thompson     const struct uaudio_terminal_node *iot, int id)
40443a3f90c6SAndrew Thompson {
4045e2524b2eSHans Petter Selasky 	const struct usb_audio_extension_unit_0 *d0 = iot[id].u.eu_v1;
40464c21be9bSRebecca Cran 	const struct usb_audio_extension_unit_1 *d1 =
40473a3f90c6SAndrew Thompson 	    (const void *)(d0->baSourceId + d0->bNrInPins);
40483a3f90c6SAndrew Thompson 
40493a3f90c6SAndrew Thompson 	DPRINTFN(3, "bUnitId=%d bNrInPins=%d\n",
40503a3f90c6SAndrew Thompson 	    d0->bUnitId, d0->bNrInPins);
40513a3f90c6SAndrew Thompson 
40523a3f90c6SAndrew Thompson 	if (sc->sc_uq_au_no_xu) {
40533a3f90c6SAndrew Thompson 		return;
40543a3f90c6SAndrew Thompson 	}
40553a3f90c6SAndrew Thompson 	if (d1->bControlSize == 0) {
40563a3f90c6SAndrew Thompson 		return;
40573a3f90c6SAndrew Thompson 	}
40583a3f90c6SAndrew Thompson 	if (d1->bmControls[0] & UA_EXT_ENABLE_MASK) {
4059f7e62ad0SHans Petter Selasky 		memset(&MIX(sc), 0, sizeof(MIX(sc)));
40603a3f90c6SAndrew Thompson 
4061f7e62ad0SHans Petter Selasky 		MIX(sc).wIndex = MAKE_WORD(d0->bUnitId, sc->sc_mixer_iface_no);
4062f7e62ad0SHans Petter Selasky 		MIX(sc).nchan = 1;
4063f7e62ad0SHans Petter Selasky 		MIX(sc).wValue[0] = MAKE_WORD(UA_EXT_ENABLE, 0);
4064f7e62ad0SHans Petter Selasky 		MIX(sc).type = MIX_ON_OFF;
40653a3f90c6SAndrew Thompson 
4066f7e62ad0SHans Petter Selasky 		uaudio_mixer_add_ctl(sc, &MIX(sc));
40673a3f90c6SAndrew Thompson 	}
40683a3f90c6SAndrew Thompson }
40693a3f90c6SAndrew Thompson 
40703a3f90c6SAndrew Thompson static const void *
uaudio_mixer_verify_desc(const void * arg,uint32_t len)40713a3f90c6SAndrew Thompson uaudio_mixer_verify_desc(const void *arg, uint32_t len)
40723a3f90c6SAndrew Thompson {
40734c21be9bSRebecca Cran 	const struct usb_audio_mixer_unit_1 *d1;
40744c21be9bSRebecca Cran 	const struct usb_audio_extension_unit_1 *e1;
40754c21be9bSRebecca Cran 	const struct usb_audio_processing_unit_1 *u1;
40763a3f90c6SAndrew Thompson 
40773a3f90c6SAndrew Thompson 	union {
4078760bc48eSAndrew Thompson 		const struct usb_descriptor *desc;
40794c21be9bSRebecca Cran 		const struct usb_audio_input_terminal *it;
40804c21be9bSRebecca Cran 		const struct usb_audio_output_terminal *ot;
40814c21be9bSRebecca Cran 		const struct usb_audio_mixer_unit_0 *mu;
40824c21be9bSRebecca Cran 		const struct usb_audio_selector_unit *su;
40834c21be9bSRebecca Cran 		const struct usb_audio_feature_unit *fu;
40844c21be9bSRebecca Cran 		const struct usb_audio_processing_unit_0 *pu;
40854c21be9bSRebecca Cran 		const struct usb_audio_extension_unit_0 *eu;
40863a3f90c6SAndrew Thompson 	}     u;
40873a3f90c6SAndrew Thompson 
40883a3f90c6SAndrew Thompson 	u.desc = arg;
40893a3f90c6SAndrew Thompson 
40903a3f90c6SAndrew Thompson 	if (u.desc == NULL) {
40913a3f90c6SAndrew Thompson 		goto error;
40923a3f90c6SAndrew Thompson 	}
40933a3f90c6SAndrew Thompson 	if (u.desc->bDescriptorType != UDESC_CS_INTERFACE) {
40943a3f90c6SAndrew Thompson 		goto error;
40953a3f90c6SAndrew Thompson 	}
40963a3f90c6SAndrew Thompson 	switch (u.desc->bDescriptorSubtype) {
40973a3f90c6SAndrew Thompson 	case UDESCSUB_AC_INPUT:
40983a3f90c6SAndrew Thompson 		len += sizeof(*u.it);
40993a3f90c6SAndrew Thompson 		break;
41003a3f90c6SAndrew Thompson 
41013a3f90c6SAndrew Thompson 	case UDESCSUB_AC_OUTPUT:
41023a3f90c6SAndrew Thompson 		len += sizeof(*u.ot);
41033a3f90c6SAndrew Thompson 		break;
41043a3f90c6SAndrew Thompson 
41053a3f90c6SAndrew Thompson 	case UDESCSUB_AC_MIXER:
41063a3f90c6SAndrew Thompson 		len += sizeof(*u.mu);
41073a3f90c6SAndrew Thompson 
41083a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
41093a3f90c6SAndrew Thompson 			goto error;
41103a3f90c6SAndrew Thompson 		}
41113a3f90c6SAndrew Thompson 		len += u.mu->bNrInPins;
41123a3f90c6SAndrew Thompson 
41133a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
41143a3f90c6SAndrew Thompson 			goto error;
41153a3f90c6SAndrew Thompson 		}
41163a3f90c6SAndrew Thompson 		d1 = (const void *)(u.mu->baSourceId + u.mu->bNrInPins);
41173a3f90c6SAndrew Thompson 
41183a3f90c6SAndrew Thompson 		len += sizeof(*d1);
41193a3f90c6SAndrew Thompson 		break;
41203a3f90c6SAndrew Thompson 
41213a3f90c6SAndrew Thompson 	case UDESCSUB_AC_SELECTOR:
41223a3f90c6SAndrew Thompson 		len += sizeof(*u.su);
41233a3f90c6SAndrew Thompson 
41243a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
41253a3f90c6SAndrew Thompson 			goto error;
41263a3f90c6SAndrew Thompson 		}
4127902514f6SHans Petter Selasky 		len += u.su->bNrInPins + 1;
41283a3f90c6SAndrew Thompson 		break;
41293a3f90c6SAndrew Thompson 
41303a3f90c6SAndrew Thompson 	case UDESCSUB_AC_FEATURE:
4131902514f6SHans Petter Selasky 		len += sizeof(*u.fu) + 1;
4132902514f6SHans Petter Selasky 
4133902514f6SHans Petter Selasky 		if (u.desc->bLength < len)
4134902514f6SHans Petter Selasky 			goto error;
4135902514f6SHans Petter Selasky 
4136902514f6SHans Petter Selasky 		len += u.fu->bControlSize;
41373a3f90c6SAndrew Thompson 		break;
41383a3f90c6SAndrew Thompson 
41393a3f90c6SAndrew Thompson 	case UDESCSUB_AC_PROCESSING:
41403a3f90c6SAndrew Thompson 		len += sizeof(*u.pu);
41413a3f90c6SAndrew Thompson 
41423a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
41433a3f90c6SAndrew Thompson 			goto error;
41443a3f90c6SAndrew Thompson 		}
41453a3f90c6SAndrew Thompson 		len += u.pu->bNrInPins;
41463a3f90c6SAndrew Thompson 
41473a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
41483a3f90c6SAndrew Thompson 			goto error;
41493a3f90c6SAndrew Thompson 		}
41503a3f90c6SAndrew Thompson 		u1 = (const void *)(u.pu->baSourceId + u.pu->bNrInPins);
41513a3f90c6SAndrew Thompson 
41523a3f90c6SAndrew Thompson 		len += sizeof(*u1);
41533a3f90c6SAndrew Thompson 
41543a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
41553a3f90c6SAndrew Thompson 			goto error;
41563a3f90c6SAndrew Thompson 		}
41573a3f90c6SAndrew Thompson 		len += u1->bControlSize;
41583a3f90c6SAndrew Thompson 
41593a3f90c6SAndrew Thompson 		break;
41603a3f90c6SAndrew Thompson 
41613a3f90c6SAndrew Thompson 	case UDESCSUB_AC_EXTENSION:
41623a3f90c6SAndrew Thompson 		len += sizeof(*u.eu);
41633a3f90c6SAndrew Thompson 
41643a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
41653a3f90c6SAndrew Thompson 			goto error;
41663a3f90c6SAndrew Thompson 		}
41673a3f90c6SAndrew Thompson 		len += u.eu->bNrInPins;
41683a3f90c6SAndrew Thompson 
41693a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
41703a3f90c6SAndrew Thompson 			goto error;
41713a3f90c6SAndrew Thompson 		}
41723a3f90c6SAndrew Thompson 		e1 = (const void *)(u.eu->baSourceId + u.eu->bNrInPins);
41733a3f90c6SAndrew Thompson 
41743a3f90c6SAndrew Thompson 		len += sizeof(*e1);
41753a3f90c6SAndrew Thompson 
41763a3f90c6SAndrew Thompson 		if (u.desc->bLength < len) {
41773a3f90c6SAndrew Thompson 			goto error;
41783a3f90c6SAndrew Thompson 		}
41793a3f90c6SAndrew Thompson 		len += e1->bControlSize;
41803a3f90c6SAndrew Thompson 		break;
41813a3f90c6SAndrew Thompson 
41823a3f90c6SAndrew Thompson 	default:
41833a3f90c6SAndrew Thompson 		goto error;
41843a3f90c6SAndrew Thompson 	}
41853a3f90c6SAndrew Thompson 
41863a3f90c6SAndrew Thompson 	if (u.desc->bLength < len) {
41873a3f90c6SAndrew Thompson 		goto error;
41883a3f90c6SAndrew Thompson 	}
41893a3f90c6SAndrew Thompson 	return (u.desc);
41903a3f90c6SAndrew Thompson 
41913a3f90c6SAndrew Thompson error:
41923a3f90c6SAndrew Thompson 	if (u.desc) {
41933a3f90c6SAndrew Thompson 		DPRINTF("invalid descriptor, type=%d, "
41943a3f90c6SAndrew Thompson 		    "sub_type=%d, len=%d of %d bytes\n",
41953a3f90c6SAndrew Thompson 		    u.desc->bDescriptorType,
41963a3f90c6SAndrew Thompson 		    u.desc->bDescriptorSubtype,
41973a3f90c6SAndrew Thompson 		    u.desc->bLength, len);
41983a3f90c6SAndrew Thompson 	}
41993a3f90c6SAndrew Thompson 	return (NULL);
42003a3f90c6SAndrew Thompson }
42013a3f90c6SAndrew Thompson 
4202e2524b2eSHans Petter Selasky static const void *
uaudio20_mixer_verify_desc(const void * arg,uint32_t len)4203e2524b2eSHans Petter Selasky uaudio20_mixer_verify_desc(const void *arg, uint32_t len)
42043a3f90c6SAndrew Thompson {
4205e2524b2eSHans Petter Selasky 	const struct usb_audio20_mixer_unit_1 *d1;
4206e2524b2eSHans Petter Selasky 	const struct usb_audio20_extension_unit_1 *e1;
4207e2524b2eSHans Petter Selasky 	const struct usb_audio20_processing_unit_1 *u1;
4208e2524b2eSHans Petter Selasky 	const struct usb_audio20_clock_selector_unit_1 *c1;
42093a3f90c6SAndrew Thompson 
4210e2524b2eSHans Petter Selasky 	union {
4211e2524b2eSHans Petter Selasky 		const struct usb_descriptor *desc;
4212e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_source_unit *csrc;
4213e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_selector_unit_0 *csel;
4214e2524b2eSHans Petter Selasky 		const struct usb_audio20_clock_multiplier_unit *cmul;
4215e2524b2eSHans Petter Selasky 		const struct usb_audio20_input_terminal *it;
4216e2524b2eSHans Petter Selasky 		const struct usb_audio20_output_terminal *ot;
4217e2524b2eSHans Petter Selasky 		const struct usb_audio20_mixer_unit_0 *mu;
4218e2524b2eSHans Petter Selasky 		const struct usb_audio20_selector_unit *su;
4219e2524b2eSHans Petter Selasky 		const struct usb_audio20_feature_unit *fu;
4220e2524b2eSHans Petter Selasky 		const struct usb_audio20_sample_rate_unit *ru;
4221e2524b2eSHans Petter Selasky 		const struct usb_audio20_processing_unit_0 *pu;
4222e2524b2eSHans Petter Selasky 		const struct usb_audio20_extension_unit_0 *eu;
4223e2524b2eSHans Petter Selasky 		const struct usb_audio20_effect_unit *ef;
4224e2524b2eSHans Petter Selasky 	}     u;
42253a3f90c6SAndrew Thompson 
4226e2524b2eSHans Petter Selasky 	u.desc = arg;
42273a3f90c6SAndrew Thompson 
4228e2524b2eSHans Petter Selasky 	if (u.desc == NULL)
4229e2524b2eSHans Petter Selasky 		goto error;
4230e2524b2eSHans Petter Selasky 
4231e2524b2eSHans Petter Selasky 	if (u.desc->bDescriptorType != UDESC_CS_INTERFACE)
4232e2524b2eSHans Petter Selasky 		goto error;
4233e2524b2eSHans Petter Selasky 
4234e2524b2eSHans Petter Selasky 	switch (u.desc->bDescriptorSubtype) {
4235e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_INPUT:
4236e2524b2eSHans Petter Selasky 		len += sizeof(*u.it);
4237e2524b2eSHans Petter Selasky 		break;
4238e2524b2eSHans Petter Selasky 
4239e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_OUTPUT:
4240e2524b2eSHans Petter Selasky 		len += sizeof(*u.ot);
4241e2524b2eSHans Petter Selasky 		break;
4242e2524b2eSHans Petter Selasky 
4243e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_MIXER:
4244e2524b2eSHans Petter Selasky 		len += sizeof(*u.mu);
4245e2524b2eSHans Petter Selasky 
4246e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
4247e2524b2eSHans Petter Selasky 			goto error;
4248e2524b2eSHans Petter Selasky 		len += u.mu->bNrInPins;
4249e2524b2eSHans Petter Selasky 
4250e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
4251e2524b2eSHans Petter Selasky 			goto error;
4252e2524b2eSHans Petter Selasky 
4253e2524b2eSHans Petter Selasky 		d1 = (const void *)(u.mu->baSourceId + u.mu->bNrInPins);
4254e2524b2eSHans Petter Selasky 
4255e2524b2eSHans Petter Selasky 		len += sizeof(*d1) + d1->bNrChannels;
4256e2524b2eSHans Petter Selasky 		break;
4257e2524b2eSHans Petter Selasky 
4258e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_SELECTOR:
4259e2524b2eSHans Petter Selasky 		len += sizeof(*u.su);
4260e2524b2eSHans Petter Selasky 
4261e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
4262e2524b2eSHans Petter Selasky 			goto error;
4263e2524b2eSHans Petter Selasky 
4264902514f6SHans Petter Selasky 		len += u.su->bNrInPins + 1;
4265e2524b2eSHans Petter Selasky 		break;
4266e2524b2eSHans Petter Selasky 
4267e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_FEATURE:
4268e2524b2eSHans Petter Selasky 		len += sizeof(*u.fu) + 1;
4269e2524b2eSHans Petter Selasky 		break;
4270e2524b2eSHans Petter Selasky 
4271e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_EFFECT:
4272e2524b2eSHans Petter Selasky 		len += sizeof(*u.ef) + 4;
4273e2524b2eSHans Petter Selasky 		break;
4274e2524b2eSHans Petter Selasky 
4275e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_PROCESSING_V2:
4276e2524b2eSHans Petter Selasky 		len += sizeof(*u.pu);
4277e2524b2eSHans Petter Selasky 
4278e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
4279e2524b2eSHans Petter Selasky 			goto error;
4280e2524b2eSHans Petter Selasky 
4281e2524b2eSHans Petter Selasky 		len += u.pu->bNrInPins;
4282e2524b2eSHans Petter Selasky 
4283e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
4284e2524b2eSHans Petter Selasky 			goto error;
4285e2524b2eSHans Petter Selasky 
4286e2524b2eSHans Petter Selasky 		u1 = (const void *)(u.pu->baSourceId + u.pu->bNrInPins);
4287e2524b2eSHans Petter Selasky 
4288e2524b2eSHans Petter Selasky 		len += sizeof(*u1);
4289e2524b2eSHans Petter Selasky 		break;
4290e2524b2eSHans Petter Selasky 
4291e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_EXTENSION_V2:
4292e2524b2eSHans Petter Selasky 		len += sizeof(*u.eu);
4293e2524b2eSHans Petter Selasky 
4294e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
4295e2524b2eSHans Petter Selasky 			goto error;
4296e2524b2eSHans Petter Selasky 
4297e2524b2eSHans Petter Selasky 		len += u.eu->bNrInPins;
4298e2524b2eSHans Petter Selasky 
4299e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
4300e2524b2eSHans Petter Selasky 			goto error;
4301e2524b2eSHans Petter Selasky 
4302e2524b2eSHans Petter Selasky 		e1 = (const void *)(u.eu->baSourceId + u.eu->bNrInPins);
4303e2524b2eSHans Petter Selasky 
4304e2524b2eSHans Petter Selasky 		len += sizeof(*e1);
4305e2524b2eSHans Petter Selasky 		break;
4306e2524b2eSHans Petter Selasky 
4307e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_CLOCK_SRC:
4308e2524b2eSHans Petter Selasky 		len += sizeof(*u.csrc);
4309e2524b2eSHans Petter Selasky 		break;
4310e2524b2eSHans Petter Selasky 
4311e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_CLOCK_SEL:
4312e2524b2eSHans Petter Selasky 		len += sizeof(*u.csel);
4313e2524b2eSHans Petter Selasky 
4314e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
4315e2524b2eSHans Petter Selasky 			goto error;
4316e2524b2eSHans Petter Selasky 
4317e2524b2eSHans Petter Selasky 		len += u.csel->bNrInPins;
4318e2524b2eSHans Petter Selasky 
4319e2524b2eSHans Petter Selasky 		if (u.desc->bLength < len)
4320e2524b2eSHans Petter Selasky 			goto error;
4321e2524b2eSHans Petter Selasky 
4322e2524b2eSHans Petter Selasky 		c1 = (const void *)(u.csel->baCSourceId + u.csel->bNrInPins);
4323e2524b2eSHans Petter Selasky 
4324e2524b2eSHans Petter Selasky 		len += sizeof(*c1);
4325e2524b2eSHans Petter Selasky 		break;
4326e2524b2eSHans Petter Selasky 
4327e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_CLOCK_MUL:
4328e2524b2eSHans Petter Selasky 		len += sizeof(*u.cmul);
4329e2524b2eSHans Petter Selasky 		break;
4330e2524b2eSHans Petter Selasky 
4331e2524b2eSHans Petter Selasky 	case UDESCSUB_AC_SAMPLE_RT:
4332e2524b2eSHans Petter Selasky 		len += sizeof(*u.ru);
4333e2524b2eSHans Petter Selasky 		break;
4334e2524b2eSHans Petter Selasky 
4335e2524b2eSHans Petter Selasky 	default:
4336e2524b2eSHans Petter Selasky 		goto error;
43373a3f90c6SAndrew Thompson 	}
43383a3f90c6SAndrew Thompson 
4339e2524b2eSHans Petter Selasky 	if (u.desc->bLength < len)
4340e2524b2eSHans Petter Selasky 		goto error;
4341e2524b2eSHans Petter Selasky 
4342e2524b2eSHans Petter Selasky 	return (u.desc);
4343e2524b2eSHans Petter Selasky 
4344e2524b2eSHans Petter Selasky error:
4345e2524b2eSHans Petter Selasky 	if (u.desc) {
4346e2524b2eSHans Petter Selasky 		DPRINTF("invalid descriptor, type=%d, "
4347e2524b2eSHans Petter Selasky 		    "sub_type=%d, len=%d of %d bytes\n",
4348e2524b2eSHans Petter Selasky 		    u.desc->bDescriptorType,
4349e2524b2eSHans Petter Selasky 		    u.desc->bDescriptorSubtype,
4350e2524b2eSHans Petter Selasky 		    u.desc->bLength, len);
4351e2524b2eSHans Petter Selasky 	}
4352e2524b2eSHans Petter Selasky 	return (NULL);
4353e2524b2eSHans Petter Selasky }
43543a3f90c6SAndrew Thompson 
43554c21be9bSRebecca Cran static struct usb_audio_cluster
uaudio_mixer_get_cluster(uint8_t id,const struct uaudio_terminal_node * iot)43563a3f90c6SAndrew Thompson uaudio_mixer_get_cluster(uint8_t id, const struct uaudio_terminal_node *iot)
43573a3f90c6SAndrew Thompson {
43584c21be9bSRebecca Cran 	struct usb_audio_cluster r;
4359760bc48eSAndrew Thompson 	const struct usb_descriptor *dp;
43603a3f90c6SAndrew Thompson 	uint8_t i;
43613a3f90c6SAndrew Thompson 
43623a3f90c6SAndrew Thompson 	for (i = 0; i < UAUDIO_RECURSE_LIMIT; i++) {	/* avoid infinite loops */
43633a3f90c6SAndrew Thompson 		dp = iot[id].u.desc;
43643a3f90c6SAndrew Thompson 		if (dp == NULL) {
43653a3f90c6SAndrew Thompson 			goto error;
43663a3f90c6SAndrew Thompson 		}
43673a3f90c6SAndrew Thompson 		switch (dp->bDescriptorSubtype) {
43683a3f90c6SAndrew Thompson 		case UDESCSUB_AC_INPUT:
4369e2524b2eSHans Petter Selasky 			r.bNrChannels = iot[id].u.it_v1->bNrChannels;
4370e2524b2eSHans Petter Selasky 			r.wChannelConfig[0] = iot[id].u.it_v1->wChannelConfig[0];
4371e2524b2eSHans Petter Selasky 			r.wChannelConfig[1] = iot[id].u.it_v1->wChannelConfig[1];
4372e2524b2eSHans Petter Selasky 			r.iChannelNames = iot[id].u.it_v1->iChannelNames;
43733a3f90c6SAndrew Thompson 			goto done;
43743a3f90c6SAndrew Thompson 
43753a3f90c6SAndrew Thompson 		case UDESCSUB_AC_OUTPUT:
4376e2524b2eSHans Petter Selasky 			id = iot[id].u.ot_v1->bSourceId;
43773a3f90c6SAndrew Thompson 			break;
43783a3f90c6SAndrew Thompson 
43793a3f90c6SAndrew Thompson 		case UDESCSUB_AC_MIXER:
43804c21be9bSRebecca Cran 			r = *(const struct usb_audio_cluster *)
4381e2524b2eSHans Petter Selasky 			    &iot[id].u.mu_v1->baSourceId[
4382e2524b2eSHans Petter Selasky 			    iot[id].u.mu_v1->bNrInPins];
43833a3f90c6SAndrew Thompson 			goto done;
43843a3f90c6SAndrew Thompson 
43853a3f90c6SAndrew Thompson 		case UDESCSUB_AC_SELECTOR:
4386e2524b2eSHans Petter Selasky 			if (iot[id].u.su_v1->bNrInPins > 0) {
43873a3f90c6SAndrew Thompson 				/* XXX This is not really right */
4388e2524b2eSHans Petter Selasky 				id = iot[id].u.su_v1->baSourceId[0];
43893a3f90c6SAndrew Thompson 			}
43903a3f90c6SAndrew Thompson 			break;
43913a3f90c6SAndrew Thompson 
43923a3f90c6SAndrew Thompson 		case UDESCSUB_AC_FEATURE:
4393e2524b2eSHans Petter Selasky 			id = iot[id].u.fu_v1->bSourceId;
43943a3f90c6SAndrew Thompson 			break;
43953a3f90c6SAndrew Thompson 
43963a3f90c6SAndrew Thompson 		case UDESCSUB_AC_PROCESSING:
43974c21be9bSRebecca Cran 			r = *((const struct usb_audio_cluster *)
4398e2524b2eSHans Petter Selasky 			    &iot[id].u.pu_v1->baSourceId[
4399e2524b2eSHans Petter Selasky 			    iot[id].u.pu_v1->bNrInPins]);
44003a3f90c6SAndrew Thompson 			goto done;
44013a3f90c6SAndrew Thompson 
44023a3f90c6SAndrew Thompson 		case UDESCSUB_AC_EXTENSION:
44034c21be9bSRebecca Cran 			r = *((const struct usb_audio_cluster *)
4404e2524b2eSHans Petter Selasky 			    &iot[id].u.eu_v1->baSourceId[
4405e2524b2eSHans Petter Selasky 			    iot[id].u.eu_v1->bNrInPins]);
44063a3f90c6SAndrew Thompson 			goto done;
44073a3f90c6SAndrew Thompson 
44083a3f90c6SAndrew Thompson 		default:
44093a3f90c6SAndrew Thompson 			goto error;
44103a3f90c6SAndrew Thompson 		}
44113a3f90c6SAndrew Thompson 	}
44123a3f90c6SAndrew Thompson error:
44133a3f90c6SAndrew Thompson 	DPRINTF("bad data\n");
44146f068a43SHans Petter Selasky 	memset(&r, 0, sizeof(r));
44153a3f90c6SAndrew Thompson done:
44163a3f90c6SAndrew Thompson 	return (r);
44173a3f90c6SAndrew Thompson }
44183a3f90c6SAndrew Thompson 
4419e2524b2eSHans Petter Selasky static struct usb_audio20_cluster
uaudio20_mixer_get_cluster(uint8_t id,const struct uaudio_terminal_node * iot)4420e2524b2eSHans Petter Selasky uaudio20_mixer_get_cluster(uint8_t id, const struct uaudio_terminal_node *iot)
44213a3f90c6SAndrew Thompson {
4422e2524b2eSHans Petter Selasky 	struct usb_audio20_cluster r;
4423e2524b2eSHans Petter Selasky 	const struct usb_descriptor *dp;
4424e2524b2eSHans Petter Selasky 	uint8_t i;
44253a3f90c6SAndrew Thompson 
4426e2524b2eSHans Petter Selasky 	for (i = 0; i < UAUDIO_RECURSE_LIMIT; i++) {	/* avoid infinite loops */
4427e2524b2eSHans Petter Selasky 		dp = iot[id].u.desc;
4428e2524b2eSHans Petter Selasky 		if (dp == NULL)
4429e2524b2eSHans Petter Selasky 			goto error;
4430e2524b2eSHans Petter Selasky 
4431e2524b2eSHans Petter Selasky 		switch (dp->bDescriptorSubtype) {
4432e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_INPUT:
4433e2524b2eSHans Petter Selasky 			r.bNrChannels = iot[id].u.it_v2->bNrChannels;
4434e2524b2eSHans Petter Selasky 			r.bmChannelConfig[0] = iot[id].u.it_v2->bmChannelConfig[0];
4435e2524b2eSHans Petter Selasky 			r.bmChannelConfig[1] = iot[id].u.it_v2->bmChannelConfig[1];
4436e2524b2eSHans Petter Selasky 			r.bmChannelConfig[2] = iot[id].u.it_v2->bmChannelConfig[2];
4437e2524b2eSHans Petter Selasky 			r.bmChannelConfig[3] = iot[id].u.it_v2->bmChannelConfig[3];
4438e2524b2eSHans Petter Selasky 			r.iChannelNames = iot[id].u.it_v2->iTerminal;
4439e2524b2eSHans Petter Selasky 			goto done;
4440e2524b2eSHans Petter Selasky 
4441e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_OUTPUT:
4442e2524b2eSHans Petter Selasky 			id = iot[id].u.ot_v2->bSourceId;
44433a3f90c6SAndrew Thompson 			break;
44443a3f90c6SAndrew Thompson 
4445e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_MIXER:
4446e2524b2eSHans Petter Selasky 			r = *(const struct usb_audio20_cluster *)
4447e2524b2eSHans Petter Selasky 			    &iot[id].u.mu_v2->baSourceId[
4448e2524b2eSHans Petter Selasky 			    iot[id].u.mu_v2->bNrInPins];
4449e2524b2eSHans Petter Selasky 			goto done;
4450e2524b2eSHans Petter Selasky 
4451e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_SELECTOR:
4452e2524b2eSHans Petter Selasky 			if (iot[id].u.su_v2->bNrInPins > 0) {
4453e2524b2eSHans Petter Selasky 				/* XXX This is not really right */
4454e2524b2eSHans Petter Selasky 				id = iot[id].u.su_v2->baSourceId[0];
4455e2524b2eSHans Petter Selasky 			}
4456e2524b2eSHans Petter Selasky 			break;
4457e2524b2eSHans Petter Selasky 
4458e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_SAMPLE_RT:
4459e2524b2eSHans Petter Selasky 			id = iot[id].u.ru_v2->bSourceId;
4460e2524b2eSHans Petter Selasky 			break;
4461e2524b2eSHans Petter Selasky 
4462e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_EFFECT:
4463e2524b2eSHans Petter Selasky 			id = iot[id].u.ef_v2->bSourceId;
4464e2524b2eSHans Petter Selasky 			break;
4465e2524b2eSHans Petter Selasky 
4466e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_FEATURE:
4467e2524b2eSHans Petter Selasky 			id = iot[id].u.fu_v2->bSourceId;
4468e2524b2eSHans Petter Selasky 			break;
4469e2524b2eSHans Petter Selasky 
4470e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_PROCESSING_V2:
4471e2524b2eSHans Petter Selasky 			r = *((const struct usb_audio20_cluster *)
4472e2524b2eSHans Petter Selasky 			    &iot[id].u.pu_v2->baSourceId[
4473e2524b2eSHans Petter Selasky 			    iot[id].u.pu_v2->bNrInPins]);
4474e2524b2eSHans Petter Selasky 			goto done;
4475e2524b2eSHans Petter Selasky 
4476e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_EXTENSION_V2:
4477e2524b2eSHans Petter Selasky 			r = *((const struct usb_audio20_cluster *)
4478e2524b2eSHans Petter Selasky 			    &iot[id].u.eu_v2->baSourceId[
4479e2524b2eSHans Petter Selasky 			    iot[id].u.eu_v2->bNrInPins]);
4480e2524b2eSHans Petter Selasky 			goto done;
4481e2524b2eSHans Petter Selasky 
4482e2524b2eSHans Petter Selasky 		default:
4483e2524b2eSHans Petter Selasky 			goto error;
4484e2524b2eSHans Petter Selasky 		}
4485e2524b2eSHans Petter Selasky 	}
4486e2524b2eSHans Petter Selasky error:
4487e2524b2eSHans Petter Selasky 	DPRINTF("Bad data!\n");
4488e2524b2eSHans Petter Selasky 	memset(&r, 0, sizeof(r));
4489e2524b2eSHans Petter Selasky done:
4490e2524b2eSHans Petter Selasky 	return (r);
4491e2524b2eSHans Petter Selasky }
44923a3f90c6SAndrew Thompson 
4493c2bc9ff7SHans Petter Selasky static bool
uaudio_mixer_foreach_input(const struct uaudio_terminal_node * iot,uint8_t * pindex)4494c2bc9ff7SHans Petter Selasky uaudio_mixer_foreach_input(const struct uaudio_terminal_node *iot, uint8_t *pindex)
44953a3f90c6SAndrew Thompson {
4496c2bc9ff7SHans Petter Selasky 	uint8_t n;
44973a3f90c6SAndrew Thompson 
4498c2bc9ff7SHans Petter Selasky 	n = *pindex;
44993a3f90c6SAndrew Thompson 
4500c2bc9ff7SHans Petter Selasky 	while (1) {
4501c2bc9ff7SHans Petter Selasky 		if (!n--)
4502c2bc9ff7SHans Petter Selasky 			n = iot->usr.id_max;
4503c2bc9ff7SHans Petter Selasky 		if (n == 0)
4504c2bc9ff7SHans Petter Selasky 			return (false);
4505c2bc9ff7SHans Petter Selasky 		if (iot->usr.bit_input[n / 8] & (1 << (n % 8)))
4506c2bc9ff7SHans Petter Selasky 			break;
45073a3f90c6SAndrew Thompson 	}
4508c2bc9ff7SHans Petter Selasky 	*pindex = n;
4509c2bc9ff7SHans Petter Selasky 	return (true);
4510e2524b2eSHans Petter Selasky }
4511e2524b2eSHans Petter Selasky 
4512c2bc9ff7SHans Petter Selasky static bool
uaudio_mixer_foreach_output(const struct uaudio_terminal_node * iot,uint8_t * pindex)4513c2bc9ff7SHans Petter Selasky uaudio_mixer_foreach_output(const struct uaudio_terminal_node *iot, uint8_t *pindex)
4514e2524b2eSHans Petter Selasky {
4515c2bc9ff7SHans Petter Selasky 	uint8_t n;
4516e2524b2eSHans Petter Selasky 
4517c2bc9ff7SHans Petter Selasky 	n = *pindex;
4518e2524b2eSHans Petter Selasky 
4519c2bc9ff7SHans Petter Selasky 	while (1) {
4520c2bc9ff7SHans Petter Selasky 		if (!n--)
4521c2bc9ff7SHans Petter Selasky 			n = iot->usr.id_max;
4522c2bc9ff7SHans Petter Selasky 		if (n == 0)
4523c2bc9ff7SHans Petter Selasky 			return (false);
4524c2bc9ff7SHans Petter Selasky 		if (iot->usr.bit_output[n / 8] & (1 << (n % 8)))
4525c2bc9ff7SHans Petter Selasky 			break;
4526e2524b2eSHans Petter Selasky 	}
4527c2bc9ff7SHans Petter Selasky 	*pindex = n;
4528c2bc9ff7SHans Petter Selasky 	return (true);
45293a3f90c6SAndrew Thompson }
45303a3f90c6SAndrew Thompson 
45313a3f90c6SAndrew Thompson struct uaudio_tt_to_feature {
45323a3f90c6SAndrew Thompson 	uint16_t terminal_type;
45333a3f90c6SAndrew Thompson 	uint16_t feature;
45343a3f90c6SAndrew Thompson };
45353a3f90c6SAndrew Thompson 
45363a3f90c6SAndrew Thompson static const struct uaudio_tt_to_feature uaudio_tt_to_feature[] = {
45373a3f90c6SAndrew Thompson 	{UATI_MICROPHONE, SOUND_MIXER_MIC},
45383a3f90c6SAndrew Thompson 	{UATI_DESKMICROPHONE, SOUND_MIXER_MIC},
45393a3f90c6SAndrew Thompson 	{UATI_PERSONALMICROPHONE, SOUND_MIXER_MIC},
45403a3f90c6SAndrew Thompson 	{UATI_OMNIMICROPHONE, SOUND_MIXER_MIC},
45413a3f90c6SAndrew Thompson 	{UATI_MICROPHONEARRAY, SOUND_MIXER_MIC},
45423a3f90c6SAndrew Thompson 	{UATI_PROCMICROPHONEARR, SOUND_MIXER_MIC},
45433a3f90c6SAndrew Thompson 
45443a3f90c6SAndrew Thompson 	{UATE_ANALOGCONN, SOUND_MIXER_LINE},
45453a3f90c6SAndrew Thompson 	{UATE_LINECONN, SOUND_MIXER_LINE},
45463a3f90c6SAndrew Thompson 	{UATE_LEGACYCONN, SOUND_MIXER_LINE},
45473a3f90c6SAndrew Thompson 
45483a3f90c6SAndrew Thompson 	{UATE_DIGITALAUIFC, SOUND_MIXER_ALTPCM},
45493a3f90c6SAndrew Thompson 	{UATE_SPDIF, SOUND_MIXER_ALTPCM},
45503a3f90c6SAndrew Thompson 	{UATE_1394DA, SOUND_MIXER_ALTPCM},
45513a3f90c6SAndrew Thompson 	{UATE_1394DV, SOUND_MIXER_ALTPCM},
45523a3f90c6SAndrew Thompson 
45533a3f90c6SAndrew Thompson 	{UATF_CDPLAYER, SOUND_MIXER_CD},
45543a3f90c6SAndrew Thompson 
45553a3f90c6SAndrew Thompson 	{UATF_SYNTHESIZER, SOUND_MIXER_SYNTH},
45563a3f90c6SAndrew Thompson 
45573a3f90c6SAndrew Thompson 	{UATF_VIDEODISCAUDIO, SOUND_MIXER_VIDEO},
45583a3f90c6SAndrew Thompson 	{UATF_DVDAUDIO, SOUND_MIXER_VIDEO},
45593a3f90c6SAndrew Thompson 	{UATF_TVTUNERAUDIO, SOUND_MIXER_VIDEO},
45603a3f90c6SAndrew Thompson 
45613a3f90c6SAndrew Thompson 	{UATF_RADIORECV, SOUND_MIXER_RADIO},
45623a3f90c6SAndrew Thompson 	{UATF_RADIOXMIT, SOUND_MIXER_RADIO},
45633a3f90c6SAndrew Thompson 
4564c2bc9ff7SHans Petter Selasky 	{}	/* END */
45653a3f90c6SAndrew Thompson };
45663a3f90c6SAndrew Thompson 
45673a3f90c6SAndrew Thompson static uint16_t
uaudio_mixer_get_feature_by_tt(uint16_t terminal_type,uint16_t default_type)4568c2bc9ff7SHans Petter Selasky uaudio_mixer_get_feature_by_tt(uint16_t terminal_type, uint16_t default_type)
4569a0be3628SHans Petter Selasky {
4570a0be3628SHans Petter Selasky 	const struct uaudio_tt_to_feature *uat = uaudio_tt_to_feature;
4571a0be3628SHans Petter Selasky 	uint16_t retval;
4572a0be3628SHans Petter Selasky 
4573c2bc9ff7SHans Petter Selasky 	if (terminal_type == 0) {
4574c2bc9ff7SHans Petter Selasky 		retval = default_type;
4575c2bc9ff7SHans Petter Selasky 	} else while (1) {
4576a0be3628SHans Petter Selasky 		if (uat->terminal_type == 0) {
4577a0be3628SHans Petter Selasky 			switch (terminal_type >> 8) {
4578a0be3628SHans Petter Selasky 			case UATI_UNDEFINED >> 8:
4579a0be3628SHans Petter Selasky 				retval = SOUND_MIXER_RECLEV;
4580a0be3628SHans Petter Selasky 				goto done;
4581a0be3628SHans Petter Selasky 			case UATO_UNDEFINED >> 8:
4582a0be3628SHans Petter Selasky 				retval = SOUND_MIXER_PCM;
4583a0be3628SHans Petter Selasky 				goto done;
4584c2bc9ff7SHans Petter Selasky 			case UATT_UNDEFINED >> 8:
4585c2bc9ff7SHans Petter Selasky 				retval = SOUND_MIXER_PHONEIN;
4586c2bc9ff7SHans Petter Selasky 				goto done;
4587a0be3628SHans Petter Selasky 			default:
4588c2bc9ff7SHans Petter Selasky 				retval = default_type;
4589a0be3628SHans Petter Selasky 				goto done;
4590a0be3628SHans Petter Selasky 			}
4591a0be3628SHans Petter Selasky 		} else if (uat->terminal_type == terminal_type) {
4592a0be3628SHans Petter Selasky 			retval = uat->feature;
4593a0be3628SHans Petter Selasky 			goto done;
4594a0be3628SHans Petter Selasky 		}
4595a0be3628SHans Petter Selasky 		uat++;
4596a0be3628SHans Petter Selasky 	}
4597a0be3628SHans Petter Selasky done:
4598c2bc9ff7SHans Petter Selasky 	DPRINTF("terminal_type=0x%04x RET=%d DEF=%d\n",
4599c2bc9ff7SHans Petter Selasky 	    terminal_type, retval, default_type);
4600a0be3628SHans Petter Selasky 	return (retval);
4601a0be3628SHans Petter Selasky }
4602a0be3628SHans Petter Selasky 
4603a0be3628SHans Petter Selasky static uint16_t
uaudio_mixer_determine_class(const struct uaudio_terminal_node * iot)4604c2bc9ff7SHans Petter Selasky uaudio_mixer_determine_class(const struct uaudio_terminal_node *iot)
46053a3f90c6SAndrew Thompson {
4606c2bc9ff7SHans Petter Selasky 	const struct uaudio_terminal_node *ptr;
4607c2bc9ff7SHans Petter Selasky 	uint16_t terminal_type_input = 0;
4608c2bc9ff7SHans Petter Selasky 	uint16_t terminal_type_output = 0;
4609c2bc9ff7SHans Petter Selasky 	uint16_t temp;
4610c2bc9ff7SHans Petter Selasky 	uint8_t match = 0;
4611c2bc9ff7SHans Petter Selasky 	uint8_t i;
46123a3f90c6SAndrew Thompson 
4613c2bc9ff7SHans Petter Selasky 	for (i = 0; uaudio_mixer_foreach_input(iot, &i); ) {
4614c2bc9ff7SHans Petter Selasky 		ptr = iot->root + i;
4615c2bc9ff7SHans Petter Selasky 		temp = UGETW(ptr->u.it_v1->wTerminalType);
4616c2bc9ff7SHans Petter Selasky 
4617c2bc9ff7SHans Petter Selasky 		if (temp == 0)
4618c2bc9ff7SHans Petter Selasky 			continue;
4619c2bc9ff7SHans Petter Selasky 		else if (temp == UAT_STREAM)
4620c2bc9ff7SHans Petter Selasky 			match |= 1;
4621c2bc9ff7SHans Petter Selasky 		else if ((temp & 0xFF00) != (UAT_UNDEFINED & 0xff00))
4622c2bc9ff7SHans Petter Selasky 			terminal_type_input = temp;
4623c2bc9ff7SHans Petter Selasky 	}
4624c2bc9ff7SHans Petter Selasky 
4625c2bc9ff7SHans Petter Selasky 	for (i = 0; uaudio_mixer_foreach_output(iot, &i); ) {
4626c2bc9ff7SHans Petter Selasky 		ptr = iot->root + i;
4627c2bc9ff7SHans Petter Selasky 		temp = UGETW(ptr->u.ot_v1->wTerminalType);
4628c2bc9ff7SHans Petter Selasky 
4629c2bc9ff7SHans Petter Selasky 		if (temp == 0)
4630c2bc9ff7SHans Petter Selasky 			continue;
4631c2bc9ff7SHans Petter Selasky 		else if (temp == UAT_STREAM)
4632c2bc9ff7SHans Petter Selasky 			match |= 2;
4633c2bc9ff7SHans Petter Selasky 		else if ((temp & 0xFF00) != (UAT_UNDEFINED & 0xff00))
4634c2bc9ff7SHans Petter Selasky 			terminal_type_output = temp;
4635c2bc9ff7SHans Petter Selasky 	}
4636c2bc9ff7SHans Petter Selasky 
4637c2bc9ff7SHans Petter Selasky 	DPRINTF("MATCH=%d IN=0x%04x OUT=0x%04x\n",
4638c2bc9ff7SHans Petter Selasky 	    match, terminal_type_input, terminal_type_output);
4639c2bc9ff7SHans Petter Selasky 
4640c2bc9ff7SHans Petter Selasky 	switch (match) {
4641c2bc9ff7SHans Petter Selasky 	case 0:	/* not connected to USB */
4642c2bc9ff7SHans Petter Selasky 		if (terminal_type_output != 0) {
4643c2bc9ff7SHans Petter Selasky 			return (uaudio_mixer_get_feature_by_tt(
4644c2bc9ff7SHans Petter Selasky 			    terminal_type_output, SOUND_MIXER_MONITOR));
4645c2bc9ff7SHans Petter Selasky 		} else {
4646c2bc9ff7SHans Petter Selasky 			return (uaudio_mixer_get_feature_by_tt(
4647c2bc9ff7SHans Petter Selasky 			    terminal_type_input, SOUND_MIXER_MONITOR));
4648c2bc9ff7SHans Petter Selasky 		}
4649c2bc9ff7SHans Petter Selasky 	case 3:	/* connected to both USB input and USB output */
46503a3f90c6SAndrew Thompson 		return (SOUND_MIXER_IMIX);
4651c2bc9ff7SHans Petter Selasky 	case 2:	/* connected to USB output */
4652c2bc9ff7SHans Petter Selasky 		return (uaudio_mixer_get_feature_by_tt(
4653c2bc9ff7SHans Petter Selasky 		    terminal_type_input, SOUND_MIXER_RECLEV));
4654c2bc9ff7SHans Petter Selasky 	case 1: /* connected to USB input */
4655c2bc9ff7SHans Petter Selasky 		return (uaudio_mixer_get_feature_by_tt(
4656c2bc9ff7SHans Petter Selasky 		    terminal_type_output, SOUND_MIXER_PCM));
4657c2bc9ff7SHans Petter Selasky 	default:
4658c2bc9ff7SHans Petter Selasky 		return (SOUND_MIXER_NRDEVICES);
4659c2bc9ff7SHans Petter Selasky 	}
4660e2524b2eSHans Petter Selasky }
4661e2524b2eSHans Petter Selasky 
4662e2524b2eSHans Petter Selasky static uint16_t
uaudio20_mixer_determine_class(const struct uaudio_terminal_node * iot)4663c2bc9ff7SHans Petter Selasky uaudio20_mixer_determine_class(const struct uaudio_terminal_node *iot)
4664e2524b2eSHans Petter Selasky {
4665c2bc9ff7SHans Petter Selasky 	const struct uaudio_terminal_node *ptr;
4666c2bc9ff7SHans Petter Selasky 	uint16_t terminal_type_input = 0;
4667c2bc9ff7SHans Petter Selasky 	uint16_t terminal_type_output = 0;
4668c2bc9ff7SHans Petter Selasky 	uint16_t temp;
4669c2bc9ff7SHans Petter Selasky 	uint8_t match = 0;
4670c2bc9ff7SHans Petter Selasky 	uint8_t i;
4671e2524b2eSHans Petter Selasky 
4672c2bc9ff7SHans Petter Selasky 	for (i = 0; uaudio_mixer_foreach_input(iot, &i); ) {
4673c2bc9ff7SHans Petter Selasky 		ptr = iot->root + i;
4674c2bc9ff7SHans Petter Selasky 		temp = UGETW(ptr->u.it_v2->wTerminalType);
4675c2bc9ff7SHans Petter Selasky 
4676c2bc9ff7SHans Petter Selasky 		if (temp == 0)
4677c2bc9ff7SHans Petter Selasky 			continue;
4678c2bc9ff7SHans Petter Selasky 		else if (temp == UAT_STREAM)
4679c2bc9ff7SHans Petter Selasky 			match |= 1;
4680c2bc9ff7SHans Petter Selasky 		else if ((temp & 0xFF00) != (UAT_UNDEFINED & 0xff00))
4681c2bc9ff7SHans Petter Selasky 			terminal_type_input = temp;
4682c2bc9ff7SHans Petter Selasky 	}
4683c2bc9ff7SHans Petter Selasky 
4684c2bc9ff7SHans Petter Selasky 	for (i = 0; uaudio_mixer_foreach_output(iot, &i); ) {
4685c2bc9ff7SHans Petter Selasky 		ptr = iot->root + i;
4686c2bc9ff7SHans Petter Selasky 		temp = UGETW(ptr->u.ot_v2->wTerminalType);
4687c2bc9ff7SHans Petter Selasky 
4688c2bc9ff7SHans Petter Selasky 		if (temp == 0)
4689c2bc9ff7SHans Petter Selasky 			continue;
4690c2bc9ff7SHans Petter Selasky 		else if (temp == UAT_STREAM)
4691c2bc9ff7SHans Petter Selasky 			match |= 2;
4692c2bc9ff7SHans Petter Selasky 		else if ((temp & 0xFF00) != (UAT_UNDEFINED & 0xff00))
4693c2bc9ff7SHans Petter Selasky 			terminal_type_output = temp;
4694c2bc9ff7SHans Petter Selasky 	}
4695c2bc9ff7SHans Petter Selasky 
4696c2bc9ff7SHans Petter Selasky 	DPRINTF("MATCH=%d IN=0x%04x OUT=0x%04x\n",
4697c2bc9ff7SHans Petter Selasky 	    match, terminal_type_input, terminal_type_output);
4698c2bc9ff7SHans Petter Selasky 
4699c2bc9ff7SHans Petter Selasky 	switch (match) {
4700c2bc9ff7SHans Petter Selasky 	case 0:	/* not connected to USB */
4701c2bc9ff7SHans Petter Selasky 		if (terminal_type_output != 0) {
4702c2bc9ff7SHans Petter Selasky 			return (uaudio_mixer_get_feature_by_tt(
4703c2bc9ff7SHans Petter Selasky 			    terminal_type_output, SOUND_MIXER_MONITOR));
4704c2bc9ff7SHans Petter Selasky 		} else {
4705c2bc9ff7SHans Petter Selasky 			return (uaudio_mixer_get_feature_by_tt(
4706c2bc9ff7SHans Petter Selasky 			    terminal_type_input, SOUND_MIXER_MONITOR));
4707c2bc9ff7SHans Petter Selasky 		}
4708c2bc9ff7SHans Petter Selasky 	case 3:	/* connected to both USB input and USB output */
4709e2524b2eSHans Petter Selasky 		return (SOUND_MIXER_IMIX);
4710c2bc9ff7SHans Petter Selasky 	case 2:	/* connected to USB output */
4711c2bc9ff7SHans Petter Selasky 		return (uaudio_mixer_get_feature_by_tt(
4712c2bc9ff7SHans Petter Selasky 		    terminal_type_input, SOUND_MIXER_RECLEV));
4713c2bc9ff7SHans Petter Selasky 	case 1: /* connected to USB input */
4714c2bc9ff7SHans Petter Selasky 		return (uaudio_mixer_get_feature_by_tt(
4715c2bc9ff7SHans Petter Selasky 		    terminal_type_output, SOUND_MIXER_PCM));
4716c2bc9ff7SHans Petter Selasky 	default:
4717c2bc9ff7SHans Petter Selasky 		return (SOUND_MIXER_NRDEVICES);
4718c2bc9ff7SHans Petter Selasky 	}
47193a3f90c6SAndrew Thompson }
47203a3f90c6SAndrew Thompson 
4721c2bc9ff7SHans Petter Selasky static void
uaudio_mixer_merge_outputs(struct uaudio_search_result * dst,const struct uaudio_search_result * src)4722c2bc9ff7SHans Petter Selasky uaudio_mixer_merge_outputs(struct uaudio_search_result *dst,
4723c2bc9ff7SHans Petter Selasky     const struct uaudio_search_result *src)
47243a3f90c6SAndrew Thompson {
4725c2bc9ff7SHans Petter Selasky 	const uint8_t max = sizeof(src->bit_output) / sizeof(src->bit_output[0]);
4726c2bc9ff7SHans Petter Selasky 	uint8_t x;
47273a3f90c6SAndrew Thompson 
4728c2bc9ff7SHans Petter Selasky 	for (x = 0; x != max; x++)
4729c2bc9ff7SHans Petter Selasky 		dst->bit_output[x] |= src->bit_output[x];
47303a3f90c6SAndrew Thompson }
47313a3f90c6SAndrew Thompson 
47323a3f90c6SAndrew Thompson static void
uaudio_mixer_find_inputs_sub(struct uaudio_terminal_node * root,const uint8_t * p_id,uint8_t n_id,struct uaudio_search_result * info)47333a3f90c6SAndrew Thompson uaudio_mixer_find_inputs_sub(struct uaudio_terminal_node *root,
47343a3f90c6SAndrew Thompson     const uint8_t *p_id, uint8_t n_id,
47353a3f90c6SAndrew Thompson     struct uaudio_search_result *info)
47363a3f90c6SAndrew Thompson {
47373a3f90c6SAndrew Thompson 	struct uaudio_terminal_node *iot;
47383a3f90c6SAndrew Thompson 	uint8_t n;
47393a3f90c6SAndrew Thompson 	uint8_t i;
47403a3f90c6SAndrew Thompson 
47413a3f90c6SAndrew Thompson 	for (n = 0; n < n_id; n++) {
47423a3f90c6SAndrew Thompson 		i = p_id[n];
47433a3f90c6SAndrew Thompson 
4744e2524b2eSHans Petter Selasky 		if (info->recurse_level == UAUDIO_RECURSE_LIMIT) {
47453a3f90c6SAndrew Thompson 			DPRINTF("avoided going into a circle at id=%d!\n", i);
4746e2524b2eSHans Petter Selasky 			return;
47473a3f90c6SAndrew Thompson 		}
47483a3f90c6SAndrew Thompson 
4749e2524b2eSHans Petter Selasky 		info->recurse_level++;
4750e2524b2eSHans Petter Selasky 
47513a3f90c6SAndrew Thompson 		iot = (root + i);
47523a3f90c6SAndrew Thompson 
4753e2524b2eSHans Petter Selasky 		if (iot->u.desc == NULL)
47543a3f90c6SAndrew Thompson 			continue;
4755e2524b2eSHans Petter Selasky 
47563a3f90c6SAndrew Thompson 		switch (iot->u.desc->bDescriptorSubtype) {
47573a3f90c6SAndrew Thompson 		case UDESCSUB_AC_INPUT:
4758c2bc9ff7SHans Petter Selasky 			uaudio_mixer_merge_outputs(&iot->usr, info);
47593a3f90c6SAndrew Thompson 			info->bit_input[i / 8] |= (1 << (i % 8));
47603a3f90c6SAndrew Thompson 			break;
47613a3f90c6SAndrew Thompson 
47623a3f90c6SAndrew Thompson 		case UDESCSUB_AC_FEATURE:
4763c2bc9ff7SHans Petter Selasky 			uaudio_mixer_merge_outputs(&iot->usr, info);
4764e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
4765e2524b2eSHans Petter Selasky 			    root, &iot->u.fu_v1->bSourceId, 1, info);
47663a3f90c6SAndrew Thompson 			break;
47673a3f90c6SAndrew Thompson 
47683a3f90c6SAndrew Thompson 		case UDESCSUB_AC_OUTPUT:
4769c2bc9ff7SHans Petter Selasky 			info->bit_output[i / 8] |= (1 << (i % 8));
4770e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
4771e2524b2eSHans Petter Selasky 			    root, &iot->u.ot_v1->bSourceId, 1, info);
4772c2bc9ff7SHans Petter Selasky 			info->bit_output[i / 8] &= ~(1 << (i % 8));
47733a3f90c6SAndrew Thompson 			break;
47743a3f90c6SAndrew Thompson 
47753a3f90c6SAndrew Thompson 		case UDESCSUB_AC_MIXER:
4776c2bc9ff7SHans Petter Selasky 			uaudio_mixer_merge_outputs(&iot->usr, info);
4777e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
4778e2524b2eSHans Petter Selasky 			    root, iot->u.mu_v1->baSourceId,
4779e2524b2eSHans Petter Selasky 			    iot->u.mu_v1->bNrInPins, info);
47803a3f90c6SAndrew Thompson 			break;
47813a3f90c6SAndrew Thompson 
47823a3f90c6SAndrew Thompson 		case UDESCSUB_AC_SELECTOR:
4783c2bc9ff7SHans Petter Selasky 			uaudio_mixer_merge_outputs(&iot->usr, info);
4784e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
4785e2524b2eSHans Petter Selasky 			    root, iot->u.su_v1->baSourceId,
4786e2524b2eSHans Petter Selasky 			    iot->u.su_v1->bNrInPins, info);
47873a3f90c6SAndrew Thompson 			break;
47883a3f90c6SAndrew Thompson 
47893a3f90c6SAndrew Thompson 		case UDESCSUB_AC_PROCESSING:
4790c2bc9ff7SHans Petter Selasky 			uaudio_mixer_merge_outputs(&iot->usr, info);
4791e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
4792e2524b2eSHans Petter Selasky 			    root, iot->u.pu_v1->baSourceId,
4793e2524b2eSHans Petter Selasky 			    iot->u.pu_v1->bNrInPins, info);
47943a3f90c6SAndrew Thompson 			break;
47953a3f90c6SAndrew Thompson 
47963a3f90c6SAndrew Thompson 		case UDESCSUB_AC_EXTENSION:
4797c2bc9ff7SHans Petter Selasky 			uaudio_mixer_merge_outputs(&iot->usr, info);
4798e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(
4799e2524b2eSHans Petter Selasky 			    root, iot->u.eu_v1->baSourceId,
4800e2524b2eSHans Petter Selasky 			    iot->u.eu_v1->bNrInPins, info);
48013a3f90c6SAndrew Thompson 			break;
48023a3f90c6SAndrew Thompson 
48033a3f90c6SAndrew Thompson 		default:
48043a3f90c6SAndrew Thompson 			break;
48053a3f90c6SAndrew Thompson 		}
48063a3f90c6SAndrew Thompson 	}
4807e2524b2eSHans Petter Selasky }
4808e2524b2eSHans Petter Selasky 
4809e2524b2eSHans Petter Selasky static void
uaudio20_mixer_find_inputs_sub(struct uaudio_terminal_node * root,const uint8_t * p_id,uint8_t n_id,struct uaudio_search_result * info)4810e2524b2eSHans Petter Selasky uaudio20_mixer_find_inputs_sub(struct uaudio_terminal_node *root,
4811e2524b2eSHans Petter Selasky     const uint8_t *p_id, uint8_t n_id,
4812e2524b2eSHans Petter Selasky     struct uaudio_search_result *info)
4813e2524b2eSHans Petter Selasky {
4814e2524b2eSHans Petter Selasky 	struct uaudio_terminal_node *iot;
4815e2524b2eSHans Petter Selasky 	uint8_t n;
4816e2524b2eSHans Petter Selasky 	uint8_t i;
4817e2524b2eSHans Petter Selasky 
4818e2524b2eSHans Petter Selasky 	for (n = 0; n < n_id; n++) {
4819e2524b2eSHans Petter Selasky 		i = p_id[n];
4820e2524b2eSHans Petter Selasky 
4821e2524b2eSHans Petter Selasky 		if (info->recurse_level == UAUDIO_RECURSE_LIMIT) {
4822e2524b2eSHans Petter Selasky 			DPRINTF("avoided going into a circle at id=%d!\n", i);
4823e2524b2eSHans Petter Selasky 			return;
4824e2524b2eSHans Petter Selasky 		}
4825e2524b2eSHans Petter Selasky 
4826e2524b2eSHans Petter Selasky 		info->recurse_level++;
4827e2524b2eSHans Petter Selasky 
4828e2524b2eSHans Petter Selasky 		iot = (root + i);
4829e2524b2eSHans Petter Selasky 
4830e2524b2eSHans Petter Selasky 		if (iot->u.desc == NULL)
4831e2524b2eSHans Petter Selasky 			continue;
4832e2524b2eSHans Petter Selasky 
4833e2524b2eSHans Petter Selasky 		switch (iot->u.desc->bDescriptorSubtype) {
4834e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_INPUT:
4835c2bc9ff7SHans Petter Selasky 			uaudio_mixer_merge_outputs(&iot->usr, info);
4836e2524b2eSHans Petter Selasky 			info->bit_input[i / 8] |= (1 << (i % 8));
4837e2524b2eSHans Petter Selasky 			break;
4838e2524b2eSHans Petter Selasky 
4839e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_OUTPUT:
4840c2bc9ff7SHans Petter Selasky 			info->bit_output[i / 8] |= (1 << (i % 8));
4841e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4842e2524b2eSHans Petter Selasky 			    root, &iot->u.ot_v2->bSourceId, 1, info);
4843c2bc9ff7SHans Petter Selasky 			info->bit_output[i / 8] &= ~(1 << (i % 8));
4844e2524b2eSHans Petter Selasky 			break;
4845e2524b2eSHans Petter Selasky 
4846e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_MIXER:
4847c2bc9ff7SHans Petter Selasky 			uaudio_mixer_merge_outputs(&iot->usr, info);
4848e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4849e2524b2eSHans Petter Selasky 			    root, iot->u.mu_v2->baSourceId,
4850e2524b2eSHans Petter Selasky 			    iot->u.mu_v2->bNrInPins, info);
4851e2524b2eSHans Petter Selasky 			break;
4852e2524b2eSHans Petter Selasky 
4853e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_SELECTOR:
4854c2bc9ff7SHans Petter Selasky 			uaudio_mixer_merge_outputs(&iot->usr, info);
4855e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4856e2524b2eSHans Petter Selasky 			    root, iot->u.su_v2->baSourceId,
4857e2524b2eSHans Petter Selasky 			    iot->u.su_v2->bNrInPins, info);
4858e2524b2eSHans Petter Selasky 			break;
4859e2524b2eSHans Petter Selasky 
4860e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_SAMPLE_RT:
4861c2bc9ff7SHans Petter Selasky 			uaudio_mixer_merge_outputs(&iot->usr, info);
4862e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4863e2524b2eSHans Petter Selasky 			    root, &iot->u.ru_v2->bSourceId,
4864e2524b2eSHans Petter Selasky 			    1, info);
4865e2524b2eSHans Petter Selasky 			break;
4866e2524b2eSHans Petter Selasky 
4867e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_EFFECT:
4868c2bc9ff7SHans Petter Selasky 			uaudio_mixer_merge_outputs(&iot->usr, info);
4869e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4870e2524b2eSHans Petter Selasky 			    root, &iot->u.ef_v2->bSourceId,
4871e2524b2eSHans Petter Selasky 			    1, info);
4872e2524b2eSHans Petter Selasky 			break;
4873e2524b2eSHans Petter Selasky 
4874e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_FEATURE:
4875c2bc9ff7SHans Petter Selasky 			uaudio_mixer_merge_outputs(&iot->usr, info);
4876e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4877e2524b2eSHans Petter Selasky 			    root, &iot->u.fu_v2->bSourceId, 1, info);
4878e2524b2eSHans Petter Selasky 			break;
4879e2524b2eSHans Petter Selasky 
4880e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_PROCESSING_V2:
4881c2bc9ff7SHans Petter Selasky 			uaudio_mixer_merge_outputs(&iot->usr, info);
4882e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4883e2524b2eSHans Petter Selasky 			    root, iot->u.pu_v2->baSourceId,
4884e2524b2eSHans Petter Selasky 			    iot->u.pu_v2->bNrInPins, info);
4885e2524b2eSHans Petter Selasky 			break;
4886e2524b2eSHans Petter Selasky 
4887e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_EXTENSION_V2:
4888c2bc9ff7SHans Petter Selasky 			uaudio_mixer_merge_outputs(&iot->usr, info);
4889e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(
4890e2524b2eSHans Petter Selasky 			    root, iot->u.eu_v2->baSourceId,
4891e2524b2eSHans Petter Selasky 			    iot->u.eu_v2->bNrInPins, info);
4892e2524b2eSHans Petter Selasky 			break;
4893e2524b2eSHans Petter Selasky 		default:
4894e2524b2eSHans Petter Selasky 			break;
4895e2524b2eSHans Petter Selasky 		}
4896e2524b2eSHans Petter Selasky 	}
4897e2524b2eSHans Petter Selasky }
4898e2524b2eSHans Petter Selasky 
4899e2524b2eSHans Petter Selasky static void
uaudio20_mixer_find_clocks_sub(struct uaudio_terminal_node * root,const uint8_t * p_id,uint8_t n_id,struct uaudio_search_result * info)4900e2524b2eSHans Petter Selasky uaudio20_mixer_find_clocks_sub(struct uaudio_terminal_node *root,
4901e2524b2eSHans Petter Selasky     const uint8_t *p_id, uint8_t n_id,
4902e2524b2eSHans Petter Selasky     struct uaudio_search_result *info)
4903e2524b2eSHans Petter Selasky {
4904e2524b2eSHans Petter Selasky 	struct uaudio_terminal_node *iot;
4905e2524b2eSHans Petter Selasky 	uint8_t n;
4906e2524b2eSHans Petter Selasky 	uint8_t i;
4907e2524b2eSHans Petter Selasky 	uint8_t is_last;
4908e2524b2eSHans Petter Selasky 	uint8_t id;
4909e2524b2eSHans Petter Selasky 
4910e2524b2eSHans Petter Selasky top:
4911e2524b2eSHans Petter Selasky 	for (n = 0; n < n_id; n++) {
4912e2524b2eSHans Petter Selasky 		i = p_id[n];
4913e2524b2eSHans Petter Selasky 
4914e2524b2eSHans Petter Selasky 		if (info->recurse_level == UAUDIO_RECURSE_LIMIT) {
4915e2524b2eSHans Petter Selasky 			DPRINTF("avoided going into a circle at id=%d!\n", i);
4916e2524b2eSHans Petter Selasky 			return;
4917e2524b2eSHans Petter Selasky 		}
4918e2524b2eSHans Petter Selasky 
4919e2524b2eSHans Petter Selasky 		info->recurse_level++;
4920e2524b2eSHans Petter Selasky 
4921e2524b2eSHans Petter Selasky 		iot = (root + i);
4922e2524b2eSHans Petter Selasky 
4923e2524b2eSHans Petter Selasky 		if (iot->u.desc == NULL)
4924e2524b2eSHans Petter Selasky 			continue;
4925e2524b2eSHans Petter Selasky 
4926e2524b2eSHans Petter Selasky 		is_last = ((n + 1) == n_id);
4927e2524b2eSHans Petter Selasky 
4928e2524b2eSHans Petter Selasky 		switch (iot->u.desc->bDescriptorSubtype) {
4929e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_INPUT:
4930e2524b2eSHans Petter Selasky 			info->is_input = 1;
4931e2524b2eSHans Petter Selasky 			if (is_last) {
4932e2524b2eSHans Petter Selasky 				p_id = &iot->u.it_v2->bCSourceId;
4933e2524b2eSHans Petter Selasky 				n_id = 1;
4934e2524b2eSHans Petter Selasky 				goto top;
4935e2524b2eSHans Petter Selasky 			}
4936e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_clocks_sub(root,
4937e2524b2eSHans Petter Selasky 			    &iot->u.it_v2->bCSourceId, 1, info);
4938e2524b2eSHans Petter Selasky 			break;
4939e2524b2eSHans Petter Selasky 
4940e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_OUTPUT:
4941e2524b2eSHans Petter Selasky 			info->is_input = 0;
4942e2524b2eSHans Petter Selasky 			if (is_last) {
4943e2524b2eSHans Petter Selasky 				p_id = &iot->u.ot_v2->bCSourceId;
4944e2524b2eSHans Petter Selasky 				n_id = 1;
4945e2524b2eSHans Petter Selasky 				goto top;
4946e2524b2eSHans Petter Selasky 			}
4947e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_clocks_sub(root,
4948e2524b2eSHans Petter Selasky 			    &iot->u.ot_v2->bCSourceId, 1, info);
4949e2524b2eSHans Petter Selasky 			break;
4950e2524b2eSHans Petter Selasky 
4951e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_CLOCK_SEL:
4952e2524b2eSHans Petter Selasky 			if (is_last) {
4953e2524b2eSHans Petter Selasky 				p_id = iot->u.csel_v2->baCSourceId;
4954e2524b2eSHans Petter Selasky 				n_id = iot->u.csel_v2->bNrInPins;
4955e2524b2eSHans Petter Selasky 				goto top;
4956e2524b2eSHans Petter Selasky 			}
4957e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_clocks_sub(root,
4958e2524b2eSHans Petter Selasky 			    iot->u.csel_v2->baCSourceId,
4959e2524b2eSHans Petter Selasky 			    iot->u.csel_v2->bNrInPins, info);
4960e2524b2eSHans Petter Selasky 			break;
4961e2524b2eSHans Petter Selasky 
4962e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_CLOCK_MUL:
4963e2524b2eSHans Petter Selasky 			if (is_last) {
4964e2524b2eSHans Petter Selasky 				p_id = &iot->u.cmul_v2->bCSourceId;
4965e2524b2eSHans Petter Selasky 				n_id = 1;
4966e2524b2eSHans Petter Selasky 				goto top;
4967e2524b2eSHans Petter Selasky 			}
4968e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_clocks_sub(root,
4969e2524b2eSHans Petter Selasky 			    &iot->u.cmul_v2->bCSourceId,
4970e2524b2eSHans Petter Selasky 			    1, info);
4971e2524b2eSHans Petter Selasky 			break;
4972e2524b2eSHans Petter Selasky 
4973e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_CLOCK_SRC:
4974e2524b2eSHans Petter Selasky 
4975e2524b2eSHans Petter Selasky 			id = iot->u.csrc_v2->bClockId;
4976e2524b2eSHans Petter Selasky 
4977e2524b2eSHans Petter Selasky 			switch (info->is_input) {
4978e2524b2eSHans Petter Selasky 			case 0:
4979e2524b2eSHans Petter Selasky 				info->bit_output[id / 8] |= (1 << (id % 8));
4980e2524b2eSHans Petter Selasky 				break;
4981e2524b2eSHans Petter Selasky 			case 1:
4982e2524b2eSHans Petter Selasky 				info->bit_input[id / 8] |= (1 << (id % 8));
4983e2524b2eSHans Petter Selasky 				break;
4984e2524b2eSHans Petter Selasky 			default:
4985e2524b2eSHans Petter Selasky 				break;
4986e2524b2eSHans Petter Selasky 			}
4987e2524b2eSHans Petter Selasky 			break;
4988e2524b2eSHans Petter Selasky 
4989e2524b2eSHans Petter Selasky 		default:
4990e2524b2eSHans Petter Selasky 			break;
4991e2524b2eSHans Petter Selasky 		}
4992e2524b2eSHans Petter Selasky 	}
49933a3f90c6SAndrew Thompson }
49943a3f90c6SAndrew Thompson 
49953a3f90c6SAndrew Thompson static void
uaudio_mixer_fill_info(struct uaudio_softc * sc,struct usb_device * udev,void * desc)4996e2524b2eSHans Petter Selasky uaudio_mixer_fill_info(struct uaudio_softc *sc,
4997e2524b2eSHans Petter Selasky     struct usb_device *udev, void *desc)
49983a3f90c6SAndrew Thompson {
49994c21be9bSRebecca Cran 	const struct usb_audio_control_descriptor *acdp;
5000a593f6b8SAndrew Thompson 	struct usb_config_descriptor *cd = usbd_get_config_descriptor(udev);
5001760bc48eSAndrew Thompson 	const struct usb_descriptor *dp;
50024c21be9bSRebecca Cran 	const struct usb_audio_unit *au;
50033a3f90c6SAndrew Thompson 	struct uaudio_terminal_node *iot = NULL;
50043a3f90c6SAndrew Thompson 	uint16_t wTotalLen;
50053a3f90c6SAndrew Thompson 	uint8_t ID_max = 0;		/* inclusive */
50063a3f90c6SAndrew Thompson 	uint8_t i;
50073a3f90c6SAndrew Thompson 
5008a593f6b8SAndrew Thompson 	desc = usb_desc_foreach(cd, desc);
50093a3f90c6SAndrew Thompson 
50103a3f90c6SAndrew Thompson 	if (desc == NULL) {
50113a3f90c6SAndrew Thompson 		DPRINTF("no Audio Control header\n");
50123a3f90c6SAndrew Thompson 		goto done;
50133a3f90c6SAndrew Thompson 	}
50143a3f90c6SAndrew Thompson 	acdp = desc;
50153a3f90c6SAndrew Thompson 
50163a3f90c6SAndrew Thompson 	if ((acdp->bLength < sizeof(*acdp)) ||
50173a3f90c6SAndrew Thompson 	    (acdp->bDescriptorType != UDESC_CS_INTERFACE) ||
50183a3f90c6SAndrew Thompson 	    (acdp->bDescriptorSubtype != UDESCSUB_AC_HEADER)) {
50193a3f90c6SAndrew Thompson 		DPRINTF("invalid Audio Control header\n");
50203a3f90c6SAndrew Thompson 		goto done;
50213a3f90c6SAndrew Thompson 	}
50223a3f90c6SAndrew Thompson 	/* "wTotalLen" is allowed to be corrupt */
50233a3f90c6SAndrew Thompson 	wTotalLen = UGETW(acdp->wTotalLength) - acdp->bLength;
50243a3f90c6SAndrew Thompson 
50253a3f90c6SAndrew Thompson 	/* get USB audio revision */
50263a3f90c6SAndrew Thompson 	sc->sc_audio_rev = UGETW(acdp->bcdADC);
50273a3f90c6SAndrew Thompson 
50283a3f90c6SAndrew Thompson 	DPRINTFN(3, "found AC header, vers=%03x, len=%d\n",
50293a3f90c6SAndrew Thompson 	    sc->sc_audio_rev, wTotalLen);
50303a3f90c6SAndrew Thompson 
50313a3f90c6SAndrew Thompson 	iot = malloc(sizeof(struct uaudio_terminal_node) * 256, M_TEMP,
50323a3f90c6SAndrew Thompson 	    M_WAITOK | M_ZERO);
50333a3f90c6SAndrew Thompson 
5034a593f6b8SAndrew Thompson 	while ((desc = usb_desc_foreach(cd, desc))) {
50353a3f90c6SAndrew Thompson 		dp = desc;
50363a3f90c6SAndrew Thompson 
50373a3f90c6SAndrew Thompson 		if (dp->bLength > wTotalLen) {
50383a3f90c6SAndrew Thompson 			break;
50393a3f90c6SAndrew Thompson 		} else {
50403a3f90c6SAndrew Thompson 			wTotalLen -= dp->bLength;
50413a3f90c6SAndrew Thompson 		}
50423a3f90c6SAndrew Thompson 
5043e2524b2eSHans Petter Selasky 		if (sc->sc_audio_rev >= UAUDIO_VERSION_30)
5044e2524b2eSHans Petter Selasky 			au = NULL;
5045e2524b2eSHans Petter Selasky 		else if (sc->sc_audio_rev >= UAUDIO_VERSION_20)
5046e2524b2eSHans Petter Selasky 			au = uaudio20_mixer_verify_desc(dp, 0);
5047e2524b2eSHans Petter Selasky 		else
50483a3f90c6SAndrew Thompson 			au = uaudio_mixer_verify_desc(dp, 0);
50493a3f90c6SAndrew Thompson 
50503a3f90c6SAndrew Thompson 		if (au) {
50513a3f90c6SAndrew Thompson 			iot[au->bUnitId].u.desc = (const void *)au;
5052e2524b2eSHans Petter Selasky 			if (au->bUnitId > ID_max)
50533a3f90c6SAndrew Thompson 				ID_max = au->bUnitId;
50543a3f90c6SAndrew Thompson 		}
50553a3f90c6SAndrew Thompson 	}
50563a3f90c6SAndrew Thompson 
50573a3f90c6SAndrew Thompson 	DPRINTF("Maximum ID=%d\n", ID_max);
50583a3f90c6SAndrew Thompson 
50593a3f90c6SAndrew Thompson 	/*
50603a3f90c6SAndrew Thompson 	 * determine sourcing inputs for
50613a3f90c6SAndrew Thompson 	 * all nodes in the tree:
50623a3f90c6SAndrew Thompson 	 */
50633a3f90c6SAndrew Thompson 	i = ID_max;
50643a3f90c6SAndrew Thompson 	do {
5065e2524b2eSHans Petter Selasky 		if (sc->sc_audio_rev >= UAUDIO_VERSION_30) {
5066e2524b2eSHans Petter Selasky 			/* FALLTHROUGH */
5067e2524b2eSHans Petter Selasky 		} else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) {
5068e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_inputs_sub(iot,
5069e2524b2eSHans Petter Selasky 			    &i, 1, &((iot + i)->usr));
5070e2524b2eSHans Petter Selasky 
5071e2524b2eSHans Petter Selasky 			sc->sc_mixer_clocks.is_input = 255;
5072e2524b2eSHans Petter Selasky 			sc->sc_mixer_clocks.recurse_level = 0;
5073e2524b2eSHans Petter Selasky 
5074e2524b2eSHans Petter Selasky 			uaudio20_mixer_find_clocks_sub(iot,
5075e2524b2eSHans Petter Selasky 			    &i, 1, &sc->sc_mixer_clocks);
5076e2524b2eSHans Petter Selasky 		} else {
5077e2524b2eSHans Petter Selasky 			uaudio_mixer_find_inputs_sub(iot,
5078e2524b2eSHans Petter Selasky 			    &i, 1, &((iot + i)->usr));
5079e2524b2eSHans Petter Selasky 		}
50803a3f90c6SAndrew Thompson 	} while (i--);
50813a3f90c6SAndrew Thompson 
50823a3f90c6SAndrew Thompson 	/* set "id_max" and "root" */
50833a3f90c6SAndrew Thompson 
50843a3f90c6SAndrew Thompson 	i = ID_max;
50853a3f90c6SAndrew Thompson 	do {
50863a3f90c6SAndrew Thompson 		(iot + i)->usr.id_max = ID_max;
50873a3f90c6SAndrew Thompson 		(iot + i)->root = iot;
50883a3f90c6SAndrew Thompson 	} while (i--);
50893a3f90c6SAndrew Thompson 
50903a3f90c6SAndrew Thompson 	/*
5091e2524b2eSHans Petter Selasky 	 * Scan the config to create a linked list of "mixer" nodes:
50923a3f90c6SAndrew Thompson 	 */
50933a3f90c6SAndrew Thompson 
50943a3f90c6SAndrew Thompson 	i = ID_max;
50953a3f90c6SAndrew Thompson 	do {
50963a3f90c6SAndrew Thompson 		dp = iot[i].u.desc;
50973a3f90c6SAndrew Thompson 
5098e2524b2eSHans Petter Selasky 		if (dp == NULL)
50993a3f90c6SAndrew Thompson 			continue;
5100e2524b2eSHans Petter Selasky 
51013a3f90c6SAndrew Thompson 		DPRINTFN(11, "id=%d subtype=%d\n",
51023a3f90c6SAndrew Thompson 		    i, dp->bDescriptorSubtype);
51033a3f90c6SAndrew Thompson 
5104e2524b2eSHans Petter Selasky 		if (sc->sc_audio_rev >= UAUDIO_VERSION_30) {
5105e2524b2eSHans Petter Selasky 			continue;
5106e2524b2eSHans Petter Selasky 		} else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) {
51073a3f90c6SAndrew Thompson 			switch (dp->bDescriptorSubtype) {
51083a3f90c6SAndrew Thompson 			case UDESCSUB_AC_HEADER:
51093a3f90c6SAndrew Thompson 				DPRINTF("unexpected AC header\n");
51103a3f90c6SAndrew Thompson 				break;
51113a3f90c6SAndrew Thompson 
51123a3f90c6SAndrew Thompson 			case UDESCSUB_AC_INPUT:
5113e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_OUTPUT:
5114e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_PROCESSING_V2:
5115e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_EXTENSION_V2:
5116e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_EFFECT:
5117e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_CLOCK_SRC:
5118e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_CLOCK_SEL:
5119e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_CLOCK_MUL:
5120e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_SAMPLE_RT:
51213a3f90c6SAndrew Thompson 				break;
51223a3f90c6SAndrew Thompson 
5123e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_MIXER:
5124e2524b2eSHans Petter Selasky 				uaudio20_mixer_add_mixer(sc, iot, i);
5125e2524b2eSHans Petter Selasky 				break;
5126e2524b2eSHans Petter Selasky 
5127e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_SELECTOR:
5128e2524b2eSHans Petter Selasky 				uaudio20_mixer_add_selector(sc, iot, i);
5129e2524b2eSHans Petter Selasky 				break;
5130e2524b2eSHans Petter Selasky 
5131e2524b2eSHans Petter Selasky 			case UDESCSUB_AC_FEATURE:
5132e2524b2eSHans Petter Selasky 				uaudio20_mixer_add_feature(sc, iot, i);
5133e2524b2eSHans Petter Selasky 				break;
5134e2524b2eSHans Petter Selasky 
5135e2524b2eSHans Petter Selasky 			default:
5136e2524b2eSHans Petter Selasky 				DPRINTF("bad AC desc subtype=0x%02x\n",
5137e2524b2eSHans Petter Selasky 				    dp->bDescriptorSubtype);
5138e2524b2eSHans Petter Selasky 				break;
5139e2524b2eSHans Petter Selasky 			}
5140e2524b2eSHans Petter Selasky 			continue;
5141e2524b2eSHans Petter Selasky 		}
5142e2524b2eSHans Petter Selasky 
5143e2524b2eSHans Petter Selasky 		switch (dp->bDescriptorSubtype) {
5144e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_HEADER:
5145e2524b2eSHans Petter Selasky 			DPRINTF("unexpected AC header\n");
5146e2524b2eSHans Petter Selasky 			break;
5147e2524b2eSHans Petter Selasky 
5148e2524b2eSHans Petter Selasky 		case UDESCSUB_AC_INPUT:
51493a3f90c6SAndrew Thompson 		case UDESCSUB_AC_OUTPUT:
51503a3f90c6SAndrew Thompson 			break;
51513a3f90c6SAndrew Thompson 
51523a3f90c6SAndrew Thompson 		case UDESCSUB_AC_MIXER:
51533a3f90c6SAndrew Thompson 			uaudio_mixer_add_mixer(sc, iot, i);
51543a3f90c6SAndrew Thompson 			break;
51553a3f90c6SAndrew Thompson 
51563a3f90c6SAndrew Thompson 		case UDESCSUB_AC_SELECTOR:
51573a3f90c6SAndrew Thompson 			uaudio_mixer_add_selector(sc, iot, i);
51583a3f90c6SAndrew Thompson 			break;
51593a3f90c6SAndrew Thompson 
51603a3f90c6SAndrew Thompson 		case UDESCSUB_AC_FEATURE:
51613a3f90c6SAndrew Thompson 			uaudio_mixer_add_feature(sc, iot, i);
51623a3f90c6SAndrew Thompson 			break;
51633a3f90c6SAndrew Thompson 
51643a3f90c6SAndrew Thompson 		case UDESCSUB_AC_PROCESSING:
51653a3f90c6SAndrew Thompson 			uaudio_mixer_add_processing(sc, iot, i);
51663a3f90c6SAndrew Thompson 			break;
51673a3f90c6SAndrew Thompson 
51683a3f90c6SAndrew Thompson 		case UDESCSUB_AC_EXTENSION:
51693a3f90c6SAndrew Thompson 			uaudio_mixer_add_extension(sc, iot, i);
51703a3f90c6SAndrew Thompson 			break;
51713a3f90c6SAndrew Thompson 
51723a3f90c6SAndrew Thompson 		default:
51733a3f90c6SAndrew Thompson 			DPRINTF("bad AC desc subtype=0x%02x\n",
51743a3f90c6SAndrew Thompson 			    dp->bDescriptorSubtype);
51753a3f90c6SAndrew Thompson 			break;
51763a3f90c6SAndrew Thompson 		}
51773a3f90c6SAndrew Thompson 
51783a3f90c6SAndrew Thompson 	} while (i--);
51793a3f90c6SAndrew Thompson 
51803a3f90c6SAndrew Thompson done:
51813a3f90c6SAndrew Thompson 	free(iot, M_TEMP);
51823a3f90c6SAndrew Thompson }
51833a3f90c6SAndrew Thompson 
5184e2524b2eSHans Petter Selasky static int
uaudio_mixer_get(struct usb_device * udev,uint16_t audio_rev,uint8_t what,struct uaudio_mixer_node * mc)5185e2524b2eSHans Petter Selasky uaudio_mixer_get(struct usb_device *udev, uint16_t audio_rev,
5186e2524b2eSHans Petter Selasky     uint8_t what, struct uaudio_mixer_node *mc)
51873a3f90c6SAndrew Thompson {
5188760bc48eSAndrew Thompson 	struct usb_device_request req;
5189e2524b2eSHans Petter Selasky 	int val;
5190e2524b2eSHans Petter Selasky 	uint8_t data[2 + (2 * 3)];
5191e0a69b51SAndrew Thompson 	usb_error_t err;
51923a3f90c6SAndrew Thompson 
5193e2524b2eSHans Petter Selasky 	if (mc->wValue[0] == -1)
51943a3f90c6SAndrew Thompson 		return (0);
5195e2524b2eSHans Petter Selasky 
5196e2524b2eSHans Petter Selasky 	if (audio_rev >= UAUDIO_VERSION_30)
5197e2524b2eSHans Petter Selasky 		return (0);
5198e2524b2eSHans Petter Selasky 	else if (audio_rev >= UAUDIO_VERSION_20) {
5199e2524b2eSHans Petter Selasky 		if (what == GET_CUR) {
5200e2524b2eSHans Petter Selasky 			req.bRequest = UA20_CS_CUR;
5201e2524b2eSHans Petter Selasky 			USETW(req.wLength, 2);
5202e2524b2eSHans Petter Selasky 		} else {
5203e2524b2eSHans Petter Selasky 			req.bRequest = UA20_CS_RANGE;
5204e2524b2eSHans Petter Selasky 			USETW(req.wLength, 8);
52053a3f90c6SAndrew Thompson 		}
5206e2524b2eSHans Petter Selasky 	} else {
5207e2524b2eSHans Petter Selasky 		uint16_t len = MIX_SIZE(mc->type);
5208e2524b2eSHans Petter Selasky 
52093a3f90c6SAndrew Thompson 		req.bRequest = what;
5210e2524b2eSHans Petter Selasky 		USETW(req.wLength, len);
5211e2524b2eSHans Petter Selasky 	}
5212e2524b2eSHans Petter Selasky 
5213e2524b2eSHans Petter Selasky 	req.bmRequestType = UT_READ_CLASS_INTERFACE;
52143a3f90c6SAndrew Thompson 	USETW(req.wValue, mc->wValue[0]);
52153a3f90c6SAndrew Thompson 	USETW(req.wIndex, mc->wIndex);
5216e2524b2eSHans Petter Selasky 
5217e2524b2eSHans Petter Selasky 	memset(data, 0, sizeof(data));
52183a3f90c6SAndrew Thompson 
5219f24b6817SAlfred Perlstein 	err = usbd_do_request(udev, NULL, &req, data);
52203a3f90c6SAndrew Thompson 	if (err) {
5221a593f6b8SAndrew Thompson 		DPRINTF("err=%s\n", usbd_errstr(err));
52223a3f90c6SAndrew Thompson 		return (0);
52233a3f90c6SAndrew Thompson 	}
5224e2524b2eSHans Petter Selasky 
5225e2524b2eSHans Petter Selasky 	if (audio_rev >= UAUDIO_VERSION_30) {
5226e2524b2eSHans Petter Selasky 		val = 0;
5227e2524b2eSHans Petter Selasky 	} else if (audio_rev >= UAUDIO_VERSION_20) {
5228e2524b2eSHans Petter Selasky 		switch (what) {
5229e2524b2eSHans Petter Selasky 		case GET_CUR:
52303a3f90c6SAndrew Thompson 			val = (data[0] | (data[1] << 8));
5231e2524b2eSHans Petter Selasky 			break;
5232e2524b2eSHans Petter Selasky 		case GET_MIN:
5233e2524b2eSHans Petter Selasky 			val = (data[2] | (data[3] << 8));
5234e2524b2eSHans Petter Selasky 			break;
5235e2524b2eSHans Petter Selasky 		case GET_MAX:
5236e2524b2eSHans Petter Selasky 			val = (data[4] | (data[5] << 8));
5237e2524b2eSHans Petter Selasky 			break;
5238e2524b2eSHans Petter Selasky 		case GET_RES:
5239e2524b2eSHans Petter Selasky 			val = (data[6] | (data[7] << 8));
5240e2524b2eSHans Petter Selasky 			break;
5241e2524b2eSHans Petter Selasky 		default:
5242e2524b2eSHans Petter Selasky 			val = 0;
5243e2524b2eSHans Petter Selasky 			break;
5244e2524b2eSHans Petter Selasky 		}
5245e2524b2eSHans Petter Selasky 	} else {
5246e2524b2eSHans Petter Selasky 		val = (data[0] | (data[1] << 8));
5247e2524b2eSHans Petter Selasky 	}
5248e2524b2eSHans Petter Selasky 
5249e2524b2eSHans Petter Selasky 	if (what == GET_CUR || what == GET_MIN || what == GET_MAX)
5250e2524b2eSHans Petter Selasky 		val = uaudio_mixer_signext(mc->type, val);
52513a3f90c6SAndrew Thompson 
52523a3f90c6SAndrew Thompson 	DPRINTFN(3, "val=%d\n", val);
52533a3f90c6SAndrew Thompson 
52543a3f90c6SAndrew Thompson 	return (val);
52553a3f90c6SAndrew Thompson }
52563a3f90c6SAndrew Thompson 
52573a3f90c6SAndrew Thompson static void
uaudio_mixer_write_cfg_callback(struct usb_xfer * xfer,usb_error_t error)5258ed6d949aSAndrew Thompson uaudio_mixer_write_cfg_callback(struct usb_xfer *xfer, usb_error_t error)
52593a3f90c6SAndrew Thompson {
5260760bc48eSAndrew Thompson 	struct usb_device_request req;
5261ed6d949aSAndrew Thompson 	struct uaudio_softc *sc = usbd_xfer_softc(xfer);
52623a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *mc = sc->sc_mixer_curr;
5263ed6d949aSAndrew Thompson 	struct usb_page_cache *pc;
52643a3f90c6SAndrew Thompson 	uint16_t len;
52653a3f90c6SAndrew Thompson 	uint8_t repeat = 1;
52663a3f90c6SAndrew Thompson 	uint8_t update;
52673a3f90c6SAndrew Thompson 	uint8_t chan;
52683a3f90c6SAndrew Thompson 	uint8_t buf[2];
52693a3f90c6SAndrew Thompson 
52703a3f90c6SAndrew Thompson 	DPRINTF("\n");
52713a3f90c6SAndrew Thompson 
52723a3f90c6SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
52733a3f90c6SAndrew Thompson 	case USB_ST_TRANSFERRED:
52743a3f90c6SAndrew Thompson tr_transferred:
52753a3f90c6SAndrew Thompson 	case USB_ST_SETUP:
52763a3f90c6SAndrew Thompson tr_setup:
52773a3f90c6SAndrew Thompson 
52783a3f90c6SAndrew Thompson 		if (mc == NULL) {
52793a3f90c6SAndrew Thompson 			mc = sc->sc_mixer_root;
52803a3f90c6SAndrew Thompson 			sc->sc_mixer_curr = mc;
52813a3f90c6SAndrew Thompson 			sc->sc_mixer_chan = 0;
52823a3f90c6SAndrew Thompson 			repeat = 0;
52833a3f90c6SAndrew Thompson 		}
52843a3f90c6SAndrew Thompson 		while (mc) {
52853a3f90c6SAndrew Thompson 			while (sc->sc_mixer_chan < mc->nchan) {
52863a3f90c6SAndrew Thompson 				chan = sc->sc_mixer_chan;
52873a3f90c6SAndrew Thompson 
52883a3f90c6SAndrew Thompson 				sc->sc_mixer_chan++;
52893a3f90c6SAndrew Thompson 
52903a3f90c6SAndrew Thompson 				update = ((mc->update[chan / 8] & (1 << (chan % 8))) &&
52913a3f90c6SAndrew Thompson 				    (mc->wValue[chan] != -1));
52923a3f90c6SAndrew Thompson 
52933a3f90c6SAndrew Thompson 				mc->update[chan / 8] &= ~(1 << (chan % 8));
52943a3f90c6SAndrew Thompson 
52953a3f90c6SAndrew Thompson 				if (update) {
52963a3f90c6SAndrew Thompson 					req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
52973a3f90c6SAndrew Thompson 					USETW(req.wValue, mc->wValue[chan]);
52983a3f90c6SAndrew Thompson 					USETW(req.wIndex, mc->wIndex);
52993a3f90c6SAndrew Thompson 
5300e2524b2eSHans Petter Selasky 					if (sc->sc_audio_rev >= UAUDIO_VERSION_30) {
5301e2524b2eSHans Petter Selasky 						return;
5302e2524b2eSHans Petter Selasky 					} else if (sc->sc_audio_rev >= UAUDIO_VERSION_20) {
5303e2524b2eSHans Petter Selasky 						len = 2;
5304e2524b2eSHans Petter Selasky 						req.bRequest = UA20_CS_CUR;
5305e2524b2eSHans Petter Selasky 						USETW(req.wLength, len);
5306e2524b2eSHans Petter Selasky 					} else {
5307e2524b2eSHans Petter Selasky 						len = MIX_SIZE(mc->type);
5308e2524b2eSHans Petter Selasky 						req.bRequest = SET_CUR;
5309e2524b2eSHans Petter Selasky 						USETW(req.wLength, len);
5310e2524b2eSHans Petter Selasky 					}
5311e2524b2eSHans Petter Selasky 
53123a3f90c6SAndrew Thompson 					buf[0] = (mc->wData[chan] & 0xFF);
53133a3f90c6SAndrew Thompson 					buf[1] = (mc->wData[chan] >> 8) & 0xFF;
5314e2524b2eSHans Petter Selasky 
5315ed6d949aSAndrew Thompson 					pc = usbd_xfer_get_frame(xfer, 0);
5316ed6d949aSAndrew Thompson 					usbd_copy_in(pc, 0, &req, sizeof(req));
5317ed6d949aSAndrew Thompson 					pc = usbd_xfer_get_frame(xfer, 1);
5318ed6d949aSAndrew Thompson 					usbd_copy_in(pc, 0, buf, len);
53193a3f90c6SAndrew Thompson 
5320ed6d949aSAndrew Thompson 					usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
5321ed6d949aSAndrew Thompson 					usbd_xfer_set_frame_len(xfer, 1, len);
5322ed6d949aSAndrew Thompson 					usbd_xfer_set_frames(xfer, len ? 2 : 1);
5323a593f6b8SAndrew Thompson 					usbd_transfer_submit(xfer);
53243a3f90c6SAndrew Thompson 					return;
53253a3f90c6SAndrew Thompson 				}
53263a3f90c6SAndrew Thompson 			}
53273a3f90c6SAndrew Thompson 
53283a3f90c6SAndrew Thompson 			mc = mc->next;
53293a3f90c6SAndrew Thompson 			sc->sc_mixer_curr = mc;
53303a3f90c6SAndrew Thompson 			sc->sc_mixer_chan = 0;
53313a3f90c6SAndrew Thompson 		}
53323a3f90c6SAndrew Thompson 
53333a3f90c6SAndrew Thompson 		if (repeat) {
53343a3f90c6SAndrew Thompson 			goto tr_setup;
53353a3f90c6SAndrew Thompson 		}
53363a3f90c6SAndrew Thompson 		break;
53373a3f90c6SAndrew Thompson 
53383a3f90c6SAndrew Thompson 	default:			/* Error */
5339ed6d949aSAndrew Thompson 		DPRINTF("error=%s\n", usbd_errstr(error));
5340ed6d949aSAndrew Thompson 		if (error == USB_ERR_CANCELLED) {
53413a3f90c6SAndrew Thompson 			/* do nothing - we are detaching */
53423a3f90c6SAndrew Thompson 			break;
53433a3f90c6SAndrew Thompson 		}
53443a3f90c6SAndrew Thompson 		goto tr_transferred;
53453a3f90c6SAndrew Thompson 	}
53463a3f90c6SAndrew Thompson }
53473a3f90c6SAndrew Thompson 
5348e0a69b51SAndrew Thompson static usb_error_t
uaudio_set_speed(struct usb_device * udev,uint8_t endpt,uint32_t speed)5349760bc48eSAndrew Thompson uaudio_set_speed(struct usb_device *udev, uint8_t endpt, uint32_t speed)
53503a3f90c6SAndrew Thompson {
5351760bc48eSAndrew Thompson 	struct usb_device_request req;
53523a3f90c6SAndrew Thompson 	uint8_t data[3];
53533a3f90c6SAndrew Thompson 
53543a3f90c6SAndrew Thompson 	DPRINTFN(6, "endpt=%d speed=%u\n", endpt, speed);
53553a3f90c6SAndrew Thompson 
53563a3f90c6SAndrew Thompson 	req.bmRequestType = UT_WRITE_CLASS_ENDPOINT;
53573a3f90c6SAndrew Thompson 	req.bRequest = SET_CUR;
53583a3f90c6SAndrew Thompson 	USETW2(req.wValue, SAMPLING_FREQ_CONTROL, 0);
53593a3f90c6SAndrew Thompson 	USETW(req.wIndex, endpt);
53603a3f90c6SAndrew Thompson 	USETW(req.wLength, 3);
53613a3f90c6SAndrew Thompson 	data[0] = speed;
53623a3f90c6SAndrew Thompson 	data[1] = speed >> 8;
53633a3f90c6SAndrew Thompson 	data[2] = speed >> 16;
53643a3f90c6SAndrew Thompson 
5365f24b6817SAlfred Perlstein 	return (usbd_do_request(udev, NULL, &req, data));
53663a3f90c6SAndrew Thompson }
53673a3f90c6SAndrew Thompson 
5368e2524b2eSHans Petter Selasky static usb_error_t
uaudio20_set_speed(struct usb_device * udev,uint8_t iface_no,uint8_t clockid,uint32_t speed)5369e2524b2eSHans Petter Selasky uaudio20_set_speed(struct usb_device *udev, uint8_t iface_no,
5370e2524b2eSHans Petter Selasky     uint8_t clockid, uint32_t speed)
5371e2524b2eSHans Petter Selasky {
5372e2524b2eSHans Petter Selasky 	struct usb_device_request req;
5373e2524b2eSHans Petter Selasky 	uint8_t data[4];
5374e2524b2eSHans Petter Selasky 
5375e2524b2eSHans Petter Selasky 	DPRINTFN(6, "ifaceno=%d clockid=%d speed=%u\n",
5376e2524b2eSHans Petter Selasky 	    iface_no, clockid, speed);
5377e2524b2eSHans Petter Selasky 
5378e2524b2eSHans Petter Selasky 	req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
5379e2524b2eSHans Petter Selasky 	req.bRequest = UA20_CS_CUR;
5380e2524b2eSHans Petter Selasky 	USETW2(req.wValue, UA20_CS_SAM_FREQ_CONTROL, 0);
5381e2524b2eSHans Petter Selasky 	USETW2(req.wIndex, clockid, iface_no);
5382e2524b2eSHans Petter Selasky 	USETW(req.wLength, 4);
5383e2524b2eSHans Petter Selasky 	data[0] = speed;
5384e2524b2eSHans Petter Selasky 	data[1] = speed >> 8;
5385e2524b2eSHans Petter Selasky 	data[2] = speed >> 16;
5386e2524b2eSHans Petter Selasky 	data[3] = speed >> 24;
5387e2524b2eSHans Petter Selasky 
5388e2524b2eSHans Petter Selasky 	return (usbd_do_request(udev, NULL, &req, data));
5389e2524b2eSHans Petter Selasky }
5390e2524b2eSHans Petter Selasky 
53913a3f90c6SAndrew Thompson static int
uaudio_mixer_signext(uint8_t type,int val)53923a3f90c6SAndrew Thompson uaudio_mixer_signext(uint8_t type, int val)
53933a3f90c6SAndrew Thompson {
53943a3f90c6SAndrew Thompson 	if (!MIX_UNSIGNED(type)) {
53953a3f90c6SAndrew Thompson 		if (MIX_SIZE(type) == 2) {
53963a3f90c6SAndrew Thompson 			val = (int16_t)val;
53973a3f90c6SAndrew Thompson 		} else {
53983a3f90c6SAndrew Thompson 			val = (int8_t)val;
53993a3f90c6SAndrew Thompson 		}
54003a3f90c6SAndrew Thompson 	}
54013a3f90c6SAndrew Thompson 	return (val);
54023a3f90c6SAndrew Thompson }
54033a3f90c6SAndrew Thompson 
54043a3f90c6SAndrew Thompson static int
uaudio_mixer_bsd2value(struct uaudio_mixer_node * mc,int val)54057e88777bSHans Petter Selasky uaudio_mixer_bsd2value(struct uaudio_mixer_node *mc, int val)
54063a3f90c6SAndrew Thompson {
54073a3f90c6SAndrew Thompson 	if (mc->type == MIX_ON_OFF) {
54083a3f90c6SAndrew Thompson 		val = (val != 0);
5409edabe1eeSHans Petter Selasky 	} else if (mc->type != MIX_SELECTOR) {
5410b029f6bbSAndrew Thompson 		/* compute actual volume */
54117e88777bSHans Petter Selasky 		val = (val * mc->mul) / 100;
5412b029f6bbSAndrew Thompson 
5413b029f6bbSAndrew Thompson 		/* add lower offset */
5414b029f6bbSAndrew Thompson 		val = val + mc->minval;
5415edabe1eeSHans Petter Selasky 	}
5416b029f6bbSAndrew Thompson 	/* make sure we don't write a value out of range */
5417b029f6bbSAndrew Thompson 	if (val > mc->maxval)
5418b029f6bbSAndrew Thompson 		val = mc->maxval;
5419b029f6bbSAndrew Thompson 	else if (val < mc->minval)
5420b029f6bbSAndrew Thompson 		val = mc->minval;
54213a3f90c6SAndrew Thompson 
54223a3f90c6SAndrew Thompson 	DPRINTFN(6, "type=0x%03x val=%d min=%d max=%d val=%d\n",
54233a3f90c6SAndrew Thompson 	    mc->type, val, mc->minval, mc->maxval, val);
54243a3f90c6SAndrew Thompson 	return (val);
54253a3f90c6SAndrew Thompson }
54263a3f90c6SAndrew Thompson 
54273a3f90c6SAndrew Thompson static void
uaudio_mixer_ctl_set(struct uaudio_softc * sc,struct uaudio_mixer_node * mc,uint8_t chan,int val)54283a3f90c6SAndrew Thompson uaudio_mixer_ctl_set(struct uaudio_softc *sc, struct uaudio_mixer_node *mc,
54297e88777bSHans Petter Selasky     uint8_t chan, int val)
54303a3f90c6SAndrew Thompson {
54313a3f90c6SAndrew Thompson 	val = uaudio_mixer_bsd2value(mc, val);
54323a3f90c6SAndrew Thompson 
54333a3f90c6SAndrew Thompson 	mc->update[chan / 8] |= (1 << (chan % 8));
54343a3f90c6SAndrew Thompson 	mc->wData[chan] = val;
54353a3f90c6SAndrew Thompson 
54363a3f90c6SAndrew Thompson 	/* start the transfer, if not already started */
54373a3f90c6SAndrew Thompson 
5438a593f6b8SAndrew Thompson 	usbd_transfer_start(sc->sc_mixer_xfer[0]);
54393a3f90c6SAndrew Thompson }
54403a3f90c6SAndrew Thompson 
54413a3f90c6SAndrew Thompson static void
uaudio_mixer_init(struct uaudio_softc * sc,unsigned index)544286c9b3f3SHans Petter Selasky uaudio_mixer_init(struct uaudio_softc *sc, unsigned index)
54433a3f90c6SAndrew Thompson {
54443a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *mc;
54453a3f90c6SAndrew Thompson 	int32_t i;
54463a3f90c6SAndrew Thompson 
544786c9b3f3SHans Petter Selasky 	if (index != 0)
544886c9b3f3SHans Petter Selasky 		return;
5449c2bc9ff7SHans Petter Selasky 	for (mc = sc->sc_mixer_root; mc; mc = mc->next) {
54503a3f90c6SAndrew Thompson 		if (mc->ctl != SOUND_MIXER_NRDEVICES) {
54513a3f90c6SAndrew Thompson 			/*
54523a3f90c6SAndrew Thompson 			 * Set device mask bits. See
54533a3f90c6SAndrew Thompson 			 * /usr/include/machine/soundcard.h
54543a3f90c6SAndrew Thompson 			 */
545586c9b3f3SHans Petter Selasky 			sc->sc_child[index].mix_info |= 1U << mc->ctl;
54563a3f90c6SAndrew Thompson 		}
54573a3f90c6SAndrew Thompson 		if ((mc->ctl == SOUND_MIXER_NRDEVICES) &&
54583a3f90c6SAndrew Thompson 		    (mc->type == MIX_SELECTOR)) {
54593a3f90c6SAndrew Thompson 			for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) {
5460c2bc9ff7SHans Petter Selasky 				if (mc->slctrtype[i - 1] == SOUND_MIXER_NRDEVICES)
54613a3f90c6SAndrew Thompson 					continue;
546286c9b3f3SHans Petter Selasky 				sc->sc_child[index].recsrc_info |= 1U << mc->slctrtype[i - 1];
54633a3f90c6SAndrew Thompson 			}
54643a3f90c6SAndrew Thompson 		}
54653a3f90c6SAndrew Thompson 	}
54663a3f90c6SAndrew Thompson }
54673a3f90c6SAndrew Thompson 
54683a3f90c6SAndrew Thompson int
uaudio_mixer_init_sub(struct uaudio_softc * sc,struct snd_mixer * m)54693a3f90c6SAndrew Thompson uaudio_mixer_init_sub(struct uaudio_softc *sc, struct snd_mixer *m)
54703a3f90c6SAndrew Thompson {
547186c9b3f3SHans Petter Selasky 	unsigned i = uaudio_get_child_index_by_dev(sc, mix_get_dev(m));
54723a3f90c6SAndrew Thompson 
547386c9b3f3SHans Petter Selasky 	DPRINTF("child=%u\n", i);
5474902514f6SHans Petter Selasky 
547586c9b3f3SHans Petter Selasky 	sc->sc_child[i].mixer_lock = mixer_get_lock(m);
547686c9b3f3SHans Petter Selasky 	sc->sc_child[i].mixer_dev = m;
547786c9b3f3SHans Petter Selasky 
547886c9b3f3SHans Petter Selasky 	if (i == 0 &&
547986c9b3f3SHans Petter Selasky 	    usbd_transfer_setup(sc->sc_udev, &sc->sc_mixer_iface_index,
54803a3f90c6SAndrew Thompson 	    sc->sc_mixer_xfer, uaudio_mixer_config, 1, sc,
548186c9b3f3SHans Petter Selasky 	    sc->sc_child[i].mixer_lock)) {
548286c9b3f3SHans Petter Selasky 		DPRINTFN(0, "could not allocate USB transfer for mixer!\n");
54833a3f90c6SAndrew Thompson 		return (ENOMEM);
54843a3f90c6SAndrew Thompson 	}
5485c2bc9ff7SHans Petter Selasky 
548686c9b3f3SHans Petter Selasky 	if (sc->sc_play_chan[i].num_alt > 0 &&
548786c9b3f3SHans Petter Selasky 	    (sc->sc_child[i].mix_info & SOUND_MASK_VOLUME) == 0) {
54883a3f90c6SAndrew Thompson 		mix_setparentchild(m, SOUND_MIXER_VOLUME, SOUND_MASK_PCM);
54893a3f90c6SAndrew Thompson 		mix_setrealdev(m, SOUND_MIXER_VOLUME, SOUND_MIXER_NONE);
54903a3f90c6SAndrew Thompson 	}
549186c9b3f3SHans Petter Selasky 	mix_setdevs(m, sc->sc_child[i].mix_info);
549286c9b3f3SHans Petter Selasky 	mix_setrecdevs(m, sc->sc_child[i].recsrc_info);
54933a3f90c6SAndrew Thompson 	return (0);
54943a3f90c6SAndrew Thompson }
54953a3f90c6SAndrew Thompson 
54963a3f90c6SAndrew Thompson int
uaudio_mixer_uninit_sub(struct uaudio_softc * sc,struct snd_mixer * m)549786c9b3f3SHans Petter Selasky uaudio_mixer_uninit_sub(struct uaudio_softc *sc, struct snd_mixer *m)
54983a3f90c6SAndrew Thompson {
549986c9b3f3SHans Petter Selasky   	unsigned index = uaudio_get_child_index_by_dev(sc, mix_get_dev(m));
55003a3f90c6SAndrew Thompson 
550186c9b3f3SHans Petter Selasky 	DPRINTF("child=%u\n", index);
550286c9b3f3SHans Petter Selasky 
550386c9b3f3SHans Petter Selasky 	if (index == 0)
5504a593f6b8SAndrew Thompson 		usbd_transfer_unsetup(sc->sc_mixer_xfer, 1);
55053a3f90c6SAndrew Thompson 
550686c9b3f3SHans Petter Selasky 	sc->sc_child[index].mixer_lock = NULL;
5507902514f6SHans Petter Selasky 
55083a3f90c6SAndrew Thompson 	return (0);
55093a3f90c6SAndrew Thompson }
55103a3f90c6SAndrew Thompson 
55113a3f90c6SAndrew Thompson void
uaudio_mixer_set(struct uaudio_softc * sc,struct snd_mixer * m,unsigned type,unsigned left,unsigned right)551286c9b3f3SHans Petter Selasky uaudio_mixer_set(struct uaudio_softc *sc, struct snd_mixer *m,
551386c9b3f3SHans Petter Selasky     unsigned type, unsigned left, unsigned right)
55143a3f90c6SAndrew Thompson {
551586c9b3f3SHans Petter Selasky     	unsigned index = uaudio_get_child_index_by_dev(sc, mix_get_dev(m));
55163a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *mc;
5517902514f6SHans Petter Selasky 	int chan;
55183a3f90c6SAndrew Thompson 
551986c9b3f3SHans Petter Selasky 	if (index != 0)
552086c9b3f3SHans Petter Selasky 		return;
5521902514f6SHans Petter Selasky 	for (mc = sc->sc_mixer_root; mc != NULL; mc = mc->next) {
55223a3f90c6SAndrew Thompson 		if (mc->ctl == type) {
5523902514f6SHans Petter Selasky 			for (chan = 0; chan < mc->nchan; chan++) {
5524902514f6SHans Petter Selasky 				uaudio_mixer_ctl_set(sc, mc, chan,
55257e88777bSHans Petter Selasky 				    chan == 0 ? left : right);
55263a3f90c6SAndrew Thompson 			}
55273a3f90c6SAndrew Thompson 		}
55283a3f90c6SAndrew Thompson 	}
55293a3f90c6SAndrew Thompson }
55303a3f90c6SAndrew Thompson 
55313a3f90c6SAndrew Thompson uint32_t
uaudio_mixer_setrecsrc(struct uaudio_softc * sc,struct snd_mixer * m,uint32_t src)553286c9b3f3SHans Petter Selasky uaudio_mixer_setrecsrc(struct uaudio_softc *sc, struct snd_mixer *m, uint32_t src)
55333a3f90c6SAndrew Thompson {
553486c9b3f3SHans Petter Selasky       	unsigned index = uaudio_get_child_index_by_dev(sc, mix_get_dev(m));
55353a3f90c6SAndrew Thompson 	struct uaudio_mixer_node *mc;
55363a3f90c6SAndrew Thompson 	uint32_t mask;
55373a3f90c6SAndrew Thompson 	uint32_t temp;
55383a3f90c6SAndrew Thompson 	int32_t i;
55393a3f90c6SAndrew Thompson 
554086c9b3f3SHans Petter Selasky 	if (index != 0)
554186c9b3f3SHans Petter Selasky 		return (0);
5542c2bc9ff7SHans Petter Selasky 	for (mc = sc->sc_mixer_root; mc; mc = mc->next) {
55433a3f90c6SAndrew Thompson 		if ((mc->ctl == SOUND_MIXER_NRDEVICES) &&
55443a3f90c6SAndrew Thompson 		    (mc->type == MIX_SELECTOR)) {
55453a3f90c6SAndrew Thompson 			/* compute selector mask */
55463a3f90c6SAndrew Thompson 
55473a3f90c6SAndrew Thompson 			mask = 0;
5548c2bc9ff7SHans Petter Selasky 			for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++)
5549c2bc9ff7SHans Petter Selasky 				mask |= 1U << mc->slctrtype[i - 1];
55503a3f90c6SAndrew Thompson 
55513a3f90c6SAndrew Thompson 			temp = mask & src;
5552c2bc9ff7SHans Petter Selasky 			if (temp == 0)
55533a3f90c6SAndrew Thompson 				continue;
5554c2bc9ff7SHans Petter Selasky 
55553a3f90c6SAndrew Thompson 			/* find the first set bit */
55563a3f90c6SAndrew Thompson 			temp = (-temp) & temp;
55573a3f90c6SAndrew Thompson 
55583a3f90c6SAndrew Thompson 			/* update "src" */
55593a3f90c6SAndrew Thompson 			src &= ~mask;
55603a3f90c6SAndrew Thompson 			src |= temp;
55613a3f90c6SAndrew Thompson 
55623a3f90c6SAndrew Thompson 			for (i = mc->minval; (i > 0) && (i <= mc->maxval); i++) {
5563c2bc9ff7SHans Petter Selasky 				if (temp != (1U << mc->slctrtype[i - 1]))
55643a3f90c6SAndrew Thompson 					continue;
55653a3f90c6SAndrew Thompson 				uaudio_mixer_ctl_set(sc, mc, 0, i);
55663a3f90c6SAndrew Thompson 				break;
55673a3f90c6SAndrew Thompson 			}
55683a3f90c6SAndrew Thompson 		}
55693a3f90c6SAndrew Thompson 	}
55703a3f90c6SAndrew Thompson 	return (src);
55713a3f90c6SAndrew Thompson }
55723a3f90c6SAndrew Thompson 
55733a3f90c6SAndrew Thompson /*========================================================================*
55743a3f90c6SAndrew Thompson  * MIDI support routines
55753a3f90c6SAndrew Thompson  *========================================================================*/
55763a3f90c6SAndrew Thompson 
55773a3f90c6SAndrew Thompson static void
umidi_bulk_read_callback(struct usb_xfer * xfer,usb_error_t error)5578ed6d949aSAndrew Thompson umidi_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
55793a3f90c6SAndrew Thompson {
5580ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usbd_xfer_softc(xfer);
55813a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub;
5582ed6d949aSAndrew Thompson 	struct usb_page_cache *pc;
55836f068a43SHans Petter Selasky 	uint8_t buf[4];
55843a3f90c6SAndrew Thompson 	uint8_t cmd_len;
55853a3f90c6SAndrew Thompson 	uint8_t cn;
55863a3f90c6SAndrew Thompson 	uint16_t pos;
5587ed6d949aSAndrew Thompson 	int actlen;
5588ed6d949aSAndrew Thompson 
5589ed6d949aSAndrew Thompson 	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
55903a3f90c6SAndrew Thompson 
55913a3f90c6SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
55923a3f90c6SAndrew Thompson 	case USB_ST_TRANSFERRED:
55933a3f90c6SAndrew Thompson 
5594ed6d949aSAndrew Thompson 		DPRINTF("actlen=%d bytes\n", actlen);
55953a3f90c6SAndrew Thompson 
55963a3f90c6SAndrew Thompson 		pos = 0;
5597ed6d949aSAndrew Thompson 		pc = usbd_xfer_get_frame(xfer, 0);
55983a3f90c6SAndrew Thompson 
5599ed6d949aSAndrew Thompson 		while (actlen >= 4) {
56006f068a43SHans Petter Selasky 			/* copy out the MIDI data */
56016f068a43SHans Petter Selasky 			usbd_copy_out(pc, pos, buf, 4);
56026f068a43SHans Petter Selasky 			/* command length */
56036f068a43SHans Petter Selasky 			cmd_len = umidi_cmd_to_len[buf[0] & 0xF];
56046f068a43SHans Petter Selasky 			/* cable number */
56056f068a43SHans Petter Selasky 			cn = buf[0] >> 4;
56066f068a43SHans Petter Selasky 			/*
56076f068a43SHans Petter Selasky 			 * Lookup sub-channel. The index is range
56086f068a43SHans Petter Selasky 			 * checked below.
56096f068a43SHans Petter Selasky 			 */
56103a3f90c6SAndrew Thompson 			sub = &chan->sub[cn];
56113a3f90c6SAndrew Thompson 
56128bf51ab5SHans Petter Selasky 			if ((cmd_len != 0) && (cn < chan->max_emb_jack) &&
56136f068a43SHans Petter Selasky 			    (sub->read_open != 0)) {
56146f068a43SHans Petter Selasky 				/* Send data to the application */
56156f068a43SHans Petter Selasky 				usb_fifo_put_data_linear(
56166f068a43SHans Petter Selasky 				    sub->fifo.fp[USB_FIFO_RX],
56176f068a43SHans Petter Selasky 				    buf + 1, cmd_len, 1);
56186f068a43SHans Petter Selasky 			}
5619ed6d949aSAndrew Thompson 			actlen -= 4;
56203a3f90c6SAndrew Thompson 			pos += 4;
56213a3f90c6SAndrew Thompson 		}
56223a3f90c6SAndrew Thompson 
56233a3f90c6SAndrew Thompson 	case USB_ST_SETUP:
56243a3f90c6SAndrew Thompson 		DPRINTF("start\n");
56256f068a43SHans Petter Selasky tr_setup:
5626ed6d949aSAndrew Thompson 		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
5627a593f6b8SAndrew Thompson 		usbd_transfer_submit(xfer);
56286f068a43SHans Petter Selasky 		break;
56293a3f90c6SAndrew Thompson 
56303a3f90c6SAndrew Thompson 	default:
5631ed6d949aSAndrew Thompson 		DPRINTF("error=%s\n", usbd_errstr(error));
56323a3f90c6SAndrew Thompson 
5633ed6d949aSAndrew Thompson 		if (error != USB_ERR_CANCELLED) {
56343a3f90c6SAndrew Thompson 			/* try to clear stall first */
56356f068a43SHans Petter Selasky 			usbd_xfer_set_stall(xfer);
56366f068a43SHans Petter Selasky 			goto tr_setup;
56373a3f90c6SAndrew Thompson 		}
56386f068a43SHans Petter Selasky 		break;
56393a3f90c6SAndrew Thompson 	}
56403a3f90c6SAndrew Thompson }
56413a3f90c6SAndrew Thompson 
56423a3f90c6SAndrew Thompson /*
56433a3f90c6SAndrew Thompson  * The following statemachine, that converts MIDI commands to
56443a3f90c6SAndrew Thompson  * USB MIDI packets, derives from Linux's usbmidi.c, which
56453a3f90c6SAndrew Thompson  * was written by "Clemens Ladisch":
56463a3f90c6SAndrew Thompson  *
56473a3f90c6SAndrew Thompson  * Returns:
56483a3f90c6SAndrew Thompson  *    0: No command
56493a3f90c6SAndrew Thompson  * Else: Command is complete
56503a3f90c6SAndrew Thompson  */
56513a3f90c6SAndrew Thompson static uint8_t
umidi_convert_to_usb(struct umidi_sub_chan * sub,uint8_t cn,uint8_t b)56523a3f90c6SAndrew Thompson umidi_convert_to_usb(struct umidi_sub_chan *sub, uint8_t cn, uint8_t b)
56533a3f90c6SAndrew Thompson {
56543a3f90c6SAndrew Thompson 	uint8_t p0 = (cn << 4);
56553a3f90c6SAndrew Thompson 
56563a3f90c6SAndrew Thompson 	if (b >= 0xf8) {
56573a3f90c6SAndrew Thompson 		sub->temp_0[0] = p0 | 0x0f;
56583a3f90c6SAndrew Thompson 		sub->temp_0[1] = b;
56593a3f90c6SAndrew Thompson 		sub->temp_0[2] = 0;
56603a3f90c6SAndrew Thompson 		sub->temp_0[3] = 0;
56613a3f90c6SAndrew Thompson 		sub->temp_cmd = sub->temp_0;
56623a3f90c6SAndrew Thompson 		return (1);
56633a3f90c6SAndrew Thompson 
56643a3f90c6SAndrew Thompson 	} else if (b >= 0xf0) {
56653a3f90c6SAndrew Thompson 		switch (b) {
56663a3f90c6SAndrew Thompson 		case 0xf0:		/* system exclusive begin */
56673a3f90c6SAndrew Thompson 			sub->temp_1[1] = b;
56683a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_SYSEX_1;
56693a3f90c6SAndrew Thompson 			break;
56703a3f90c6SAndrew Thompson 		case 0xf1:		/* MIDI time code */
56713a3f90c6SAndrew Thompson 		case 0xf3:		/* song select */
56723a3f90c6SAndrew Thompson 			sub->temp_1[1] = b;
56733a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_1PARAM;
56743a3f90c6SAndrew Thompson 			break;
56753a3f90c6SAndrew Thompson 		case 0xf2:		/* song position pointer */
56763a3f90c6SAndrew Thompson 			sub->temp_1[1] = b;
56773a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_2PARAM_1;
56783a3f90c6SAndrew Thompson 			break;
56793a3f90c6SAndrew Thompson 		case 0xf4:		/* unknown */
56803a3f90c6SAndrew Thompson 		case 0xf5:		/* unknown */
56813a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_UNKNOWN;
56823a3f90c6SAndrew Thompson 			break;
56833a3f90c6SAndrew Thompson 		case 0xf6:		/* tune request */
56843a3f90c6SAndrew Thompson 			sub->temp_1[0] = p0 | 0x05;
56853a3f90c6SAndrew Thompson 			sub->temp_1[1] = 0xf6;
56863a3f90c6SAndrew Thompson 			sub->temp_1[2] = 0;
56873a3f90c6SAndrew Thompson 			sub->temp_1[3] = 0;
56883a3f90c6SAndrew Thompson 			sub->temp_cmd = sub->temp_1;
56893a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_UNKNOWN;
56903a3f90c6SAndrew Thompson 			return (1);
56913a3f90c6SAndrew Thompson 
56923a3f90c6SAndrew Thompson 		case 0xf7:		/* system exclusive end */
56933a3f90c6SAndrew Thompson 			switch (sub->state) {
56943a3f90c6SAndrew Thompson 			case UMIDI_ST_SYSEX_0:
56953a3f90c6SAndrew Thompson 				sub->temp_1[0] = p0 | 0x05;
56963a3f90c6SAndrew Thompson 				sub->temp_1[1] = 0xf7;
56973a3f90c6SAndrew Thompson 				sub->temp_1[2] = 0;
56983a3f90c6SAndrew Thompson 				sub->temp_1[3] = 0;
56993a3f90c6SAndrew Thompson 				sub->temp_cmd = sub->temp_1;
57003a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_UNKNOWN;
57013a3f90c6SAndrew Thompson 				return (1);
57023a3f90c6SAndrew Thompson 			case UMIDI_ST_SYSEX_1:
57033a3f90c6SAndrew Thompson 				sub->temp_1[0] = p0 | 0x06;
57043a3f90c6SAndrew Thompson 				sub->temp_1[2] = 0xf7;
57053a3f90c6SAndrew Thompson 				sub->temp_1[3] = 0;
57063a3f90c6SAndrew Thompson 				sub->temp_cmd = sub->temp_1;
57073a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_UNKNOWN;
57083a3f90c6SAndrew Thompson 				return (1);
57093a3f90c6SAndrew Thompson 			case UMIDI_ST_SYSEX_2:
57103a3f90c6SAndrew Thompson 				sub->temp_1[0] = p0 | 0x07;
57113a3f90c6SAndrew Thompson 				sub->temp_1[3] = 0xf7;
57123a3f90c6SAndrew Thompson 				sub->temp_cmd = sub->temp_1;
57133a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_UNKNOWN;
57143a3f90c6SAndrew Thompson 				return (1);
57153a3f90c6SAndrew Thompson 			}
57163a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_UNKNOWN;
57173a3f90c6SAndrew Thompson 			break;
57183a3f90c6SAndrew Thompson 		}
57193a3f90c6SAndrew Thompson 	} else if (b >= 0x80) {
57203a3f90c6SAndrew Thompson 		sub->temp_1[1] = b;
57213a3f90c6SAndrew Thompson 		if ((b >= 0xc0) && (b <= 0xdf)) {
57223a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_1PARAM;
57233a3f90c6SAndrew Thompson 		} else {
57243a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_2PARAM_1;
57253a3f90c6SAndrew Thompson 		}
57263a3f90c6SAndrew Thompson 	} else {			/* b < 0x80 */
57273a3f90c6SAndrew Thompson 		switch (sub->state) {
57283a3f90c6SAndrew Thompson 		case UMIDI_ST_1PARAM:
57293a3f90c6SAndrew Thompson 			if (sub->temp_1[1] < 0xf0) {
57303a3f90c6SAndrew Thompson 				p0 |= sub->temp_1[1] >> 4;
57313a3f90c6SAndrew Thompson 			} else {
57323a3f90c6SAndrew Thompson 				p0 |= 0x02;
57333a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_UNKNOWN;
57343a3f90c6SAndrew Thompson 			}
57353a3f90c6SAndrew Thompson 			sub->temp_1[0] = p0;
57363a3f90c6SAndrew Thompson 			sub->temp_1[2] = b;
57373a3f90c6SAndrew Thompson 			sub->temp_1[3] = 0;
57383a3f90c6SAndrew Thompson 			sub->temp_cmd = sub->temp_1;
57393a3f90c6SAndrew Thompson 			return (1);
57403a3f90c6SAndrew Thompson 		case UMIDI_ST_2PARAM_1:
57413a3f90c6SAndrew Thompson 			sub->temp_1[2] = b;
57423a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_2PARAM_2;
57433a3f90c6SAndrew Thompson 			break;
57443a3f90c6SAndrew Thompson 		case UMIDI_ST_2PARAM_2:
57453a3f90c6SAndrew Thompson 			if (sub->temp_1[1] < 0xf0) {
57463a3f90c6SAndrew Thompson 				p0 |= sub->temp_1[1] >> 4;
57473a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_2PARAM_1;
57483a3f90c6SAndrew Thompson 			} else {
57493a3f90c6SAndrew Thompson 				p0 |= 0x03;
57503a3f90c6SAndrew Thompson 				sub->state = UMIDI_ST_UNKNOWN;
57513a3f90c6SAndrew Thompson 			}
57523a3f90c6SAndrew Thompson 			sub->temp_1[0] = p0;
57533a3f90c6SAndrew Thompson 			sub->temp_1[3] = b;
57543a3f90c6SAndrew Thompson 			sub->temp_cmd = sub->temp_1;
57553a3f90c6SAndrew Thompson 			return (1);
57563a3f90c6SAndrew Thompson 		case UMIDI_ST_SYSEX_0:
57573a3f90c6SAndrew Thompson 			sub->temp_1[1] = b;
57583a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_SYSEX_1;
57593a3f90c6SAndrew Thompson 			break;
57603a3f90c6SAndrew Thompson 		case UMIDI_ST_SYSEX_1:
57613a3f90c6SAndrew Thompson 			sub->temp_1[2] = b;
57623a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_SYSEX_2;
57633a3f90c6SAndrew Thompson 			break;
57643a3f90c6SAndrew Thompson 		case UMIDI_ST_SYSEX_2:
57653a3f90c6SAndrew Thompson 			sub->temp_1[0] = p0 | 0x04;
57663a3f90c6SAndrew Thompson 			sub->temp_1[3] = b;
57673a3f90c6SAndrew Thompson 			sub->temp_cmd = sub->temp_1;
57683a3f90c6SAndrew Thompson 			sub->state = UMIDI_ST_SYSEX_0;
57693a3f90c6SAndrew Thompson 			return (1);
57706f068a43SHans Petter Selasky 		default:
57716f068a43SHans Petter Selasky 			break;
57723a3f90c6SAndrew Thompson 		}
57733a3f90c6SAndrew Thompson 	}
57743a3f90c6SAndrew Thompson 	return (0);
57753a3f90c6SAndrew Thompson }
57763a3f90c6SAndrew Thompson 
57773a3f90c6SAndrew Thompson static void
umidi_bulk_write_callback(struct usb_xfer * xfer,usb_error_t error)5778ed6d949aSAndrew Thompson umidi_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
57793a3f90c6SAndrew Thompson {
5780ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usbd_xfer_softc(xfer);
57813a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub;
5782ed6d949aSAndrew Thompson 	struct usb_page_cache *pc;
57833a3f90c6SAndrew Thompson 	uint32_t actlen;
5784910f1dcfSHans Petter Selasky 	uint16_t nframes;
57853a3f90c6SAndrew Thompson 	uint8_t buf;
57863a3f90c6SAndrew Thompson 	uint8_t start_cable;
57873a3f90c6SAndrew Thompson 	uint8_t tr_any;
5788ed6d949aSAndrew Thompson 	int len;
5789ed6d949aSAndrew Thompson 
5790ed6d949aSAndrew Thompson 	usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
57913a3f90c6SAndrew Thompson 
5792910f1dcfSHans Petter Selasky 	/*
5793910f1dcfSHans Petter Selasky 	 * NOTE: Some MIDI devices only accept 4 bytes of data per
5794910f1dcfSHans Petter Selasky 	 * short terminated USB transfer.
5795910f1dcfSHans Petter Selasky 	 */
57963a3f90c6SAndrew Thompson 	switch (USB_GET_STATE(xfer)) {
57973a3f90c6SAndrew Thompson 	case USB_ST_TRANSFERRED:
5798ed6d949aSAndrew Thompson 		DPRINTF("actlen=%d bytes\n", len);
57993a3f90c6SAndrew Thompson 
58003a3f90c6SAndrew Thompson 	case USB_ST_SETUP:
58016f068a43SHans Petter Selasky tr_setup:
58023a3f90c6SAndrew Thompson 		DPRINTF("start\n");
58033a3f90c6SAndrew Thompson 
5804910f1dcfSHans Petter Selasky 		nframes = 0;	/* reset */
58053a3f90c6SAndrew Thompson 		start_cable = chan->curr_cable;
58063a3f90c6SAndrew Thompson 		tr_any = 0;
58074944c3a8SHans Petter Selasky 		pc = usbd_xfer_get_frame(xfer, 0);
58083a3f90c6SAndrew Thompson 
58093a3f90c6SAndrew Thompson 		while (1) {
58103a3f90c6SAndrew Thompson 			/* round robin de-queueing */
58113a3f90c6SAndrew Thompson 
58123a3f90c6SAndrew Thompson 			sub = &chan->sub[chan->curr_cable];
58133a3f90c6SAndrew Thompson 
58143a3f90c6SAndrew Thompson 			if (sub->write_open) {
5815910f1dcfSHans Petter Selasky 				usb_fifo_get_data_linear(sub->fifo.fp[USB_FIFO_TX],
5816910f1dcfSHans Petter Selasky 				    &buf, 1, &actlen, 0);
58173a3f90c6SAndrew Thompson 			} else {
58183a3f90c6SAndrew Thompson 				actlen = 0;
58193a3f90c6SAndrew Thompson 			}
58203a3f90c6SAndrew Thompson 
58213a3f90c6SAndrew Thompson 			if (actlen) {
58223a3f90c6SAndrew Thompson 				tr_any = 1;
58233a3f90c6SAndrew Thompson 
5824910f1dcfSHans Petter Selasky 				DPRINTF("byte=0x%02x from FIFO %u\n", buf,
5825910f1dcfSHans Petter Selasky 				    (unsigned int)chan->curr_cable);
58263a3f90c6SAndrew Thompson 
58273a3f90c6SAndrew Thompson 				if (umidi_convert_to_usb(sub, chan->curr_cable, buf)) {
5828910f1dcfSHans Petter Selasky 					DPRINTF("sub=0x%02x 0x%02x 0x%02x 0x%02x\n",
58293a3f90c6SAndrew Thompson 					    sub->temp_cmd[0], sub->temp_cmd[1],
58303a3f90c6SAndrew Thompson 					    sub->temp_cmd[2], sub->temp_cmd[3]);
58313a3f90c6SAndrew Thompson 
58324944c3a8SHans Petter Selasky 					usbd_copy_in(pc, nframes * 4, sub->temp_cmd, 4);
5833910f1dcfSHans Petter Selasky 
5834910f1dcfSHans Petter Selasky 					nframes++;
58354944c3a8SHans Petter Selasky 
58364944c3a8SHans Petter Selasky 					if ((nframes >= UMIDI_TX_FRAMES) || (chan->single_command != 0))
58373a3f90c6SAndrew Thompson 						break;
58383a3f90c6SAndrew Thompson 				} else {
58393a3f90c6SAndrew Thompson 					continue;
58403a3f90c6SAndrew Thompson 				}
58413a3f90c6SAndrew Thompson 			}
5842910f1dcfSHans Petter Selasky 
58433a3f90c6SAndrew Thompson 			chan->curr_cable++;
58448bf51ab5SHans Petter Selasky 			if (chan->curr_cable >= chan->max_emb_jack)
58453a3f90c6SAndrew Thompson 				chan->curr_cable = 0;
5846910f1dcfSHans Petter Selasky 
58473a3f90c6SAndrew Thompson 			if (chan->curr_cable == start_cable) {
5848910f1dcfSHans Petter Selasky 				if (tr_any == 0)
58493a3f90c6SAndrew Thompson 					break;
58503a3f90c6SAndrew Thompson 				tr_any = 0;
58513a3f90c6SAndrew Thompson 			}
58523a3f90c6SAndrew Thompson 		}
58533a3f90c6SAndrew Thompson 
58544944c3a8SHans Petter Selasky 		if (nframes != 0) {
5855910f1dcfSHans Petter Selasky 			DPRINTF("Transferring %d frames\n", (int)nframes);
58564944c3a8SHans Petter Selasky 			usbd_xfer_set_frame_len(xfer, 0, 4 * nframes);
5857a593f6b8SAndrew Thompson 			usbd_transfer_submit(xfer);
58583a3f90c6SAndrew Thompson 		}
58596f068a43SHans Petter Selasky 		break;
58603a3f90c6SAndrew Thompson 
58613a3f90c6SAndrew Thompson 	default:			/* Error */
58623a3f90c6SAndrew Thompson 
5863ed6d949aSAndrew Thompson 		DPRINTF("error=%s\n", usbd_errstr(error));
58643a3f90c6SAndrew Thompson 
5865ed6d949aSAndrew Thompson 		if (error != USB_ERR_CANCELLED) {
58663a3f90c6SAndrew Thompson 			/* try to clear stall first */
58676f068a43SHans Petter Selasky 			usbd_xfer_set_stall(xfer);
58686f068a43SHans Petter Selasky 			goto tr_setup;
58693a3f90c6SAndrew Thompson 		}
58706f068a43SHans Petter Selasky 		break;
58713a3f90c6SAndrew Thompson 	}
58723a3f90c6SAndrew Thompson }
58733a3f90c6SAndrew Thompson 
58743a3f90c6SAndrew Thompson static struct umidi_sub_chan *
umidi_sub_by_fifo(struct usb_fifo * fifo)5875760bc48eSAndrew Thompson umidi_sub_by_fifo(struct usb_fifo *fifo)
58763a3f90c6SAndrew Thompson {
5877ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
58783a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub;
58793a3f90c6SAndrew Thompson 	uint32_t n;
58803a3f90c6SAndrew Thompson 
58818bf51ab5SHans Petter Selasky 	for (n = 0; n < UMIDI_EMB_JACK_MAX; n++) {
58823a3f90c6SAndrew Thompson 		sub = &chan->sub[n];
58833a3f90c6SAndrew Thompson 		if ((sub->fifo.fp[USB_FIFO_RX] == fifo) ||
58843a3f90c6SAndrew Thompson 		    (sub->fifo.fp[USB_FIFO_TX] == fifo)) {
58853a3f90c6SAndrew Thompson 			return (sub);
58863a3f90c6SAndrew Thompson 		}
58873a3f90c6SAndrew Thompson 	}
58883a3f90c6SAndrew Thompson 
5889760bc48eSAndrew Thompson 	panic("%s:%d cannot find usb_fifo!\n",
58903a3f90c6SAndrew Thompson 	    __FILE__, __LINE__);
58913a3f90c6SAndrew Thompson 
58923a3f90c6SAndrew Thompson 	return (NULL);
58933a3f90c6SAndrew Thompson }
58943a3f90c6SAndrew Thompson 
58953a3f90c6SAndrew Thompson static void
umidi_start_read(struct usb_fifo * fifo)5896760bc48eSAndrew Thompson umidi_start_read(struct usb_fifo *fifo)
58973a3f90c6SAndrew Thompson {
5898ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
58993a3f90c6SAndrew Thompson 
59006f068a43SHans Petter Selasky 	usbd_transfer_start(chan->xfer[UMIDI_RX_TRANSFER]);
59013a3f90c6SAndrew Thompson }
59023a3f90c6SAndrew Thompson 
59033a3f90c6SAndrew Thompson static void
umidi_stop_read(struct usb_fifo * fifo)5904760bc48eSAndrew Thompson umidi_stop_read(struct usb_fifo *fifo)
59053a3f90c6SAndrew Thompson {
5906ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
59073a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo);
59083a3f90c6SAndrew Thompson 
59093a3f90c6SAndrew Thompson 	DPRINTF("\n");
59103a3f90c6SAndrew Thompson 
59113a3f90c6SAndrew Thompson 	sub->read_open = 0;
59123a3f90c6SAndrew Thompson 
59133a3f90c6SAndrew Thompson 	if (--(chan->read_open_refcount) == 0) {
59143a3f90c6SAndrew Thompson 		/*
59153a3f90c6SAndrew Thompson 		 * XXX don't stop the read transfer here, hence that causes
59163a3f90c6SAndrew Thompson 		 * problems with some MIDI adapters
59173a3f90c6SAndrew Thompson 		 */
59183a3f90c6SAndrew Thompson 		DPRINTF("(stopping read transfer)\n");
59193a3f90c6SAndrew Thompson 	}
59203a3f90c6SAndrew Thompson }
59213a3f90c6SAndrew Thompson 
59223a3f90c6SAndrew Thompson static void
umidi_start_write(struct usb_fifo * fifo)5923760bc48eSAndrew Thompson umidi_start_write(struct usb_fifo *fifo)
59243a3f90c6SAndrew Thompson {
5925ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
59263a3f90c6SAndrew Thompson 
5927d56d9e27SHans Petter Selasky 	if (chan->xfer[UMIDI_TX_TRANSFER] == NULL) {
5928d56d9e27SHans Petter Selasky 		uint8_t buf[1];
5929d56d9e27SHans Petter Selasky 		int actlen;
5930d56d9e27SHans Petter Selasky 		do {
5931d56d9e27SHans Petter Selasky 			/* dump data */
5932d56d9e27SHans Petter Selasky 			usb_fifo_get_data_linear(fifo, buf, 1, &actlen, 0);
5933d56d9e27SHans Petter Selasky 		} while (actlen > 0);
5934d56d9e27SHans Petter Selasky 	} else {
59356f068a43SHans Petter Selasky 		usbd_transfer_start(chan->xfer[UMIDI_TX_TRANSFER]);
59363a3f90c6SAndrew Thompson 	}
5937d56d9e27SHans Petter Selasky }
59383a3f90c6SAndrew Thompson 
59393a3f90c6SAndrew Thompson static void
umidi_stop_write(struct usb_fifo * fifo)5940760bc48eSAndrew Thompson umidi_stop_write(struct usb_fifo *fifo)
59413a3f90c6SAndrew Thompson {
5942ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
59433a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo);
59443a3f90c6SAndrew Thompson 
59453a3f90c6SAndrew Thompson 	DPRINTF("\n");
59463a3f90c6SAndrew Thompson 
59473a3f90c6SAndrew Thompson 	sub->write_open = 0;
59483a3f90c6SAndrew Thompson 
59493a3f90c6SAndrew Thompson 	if (--(chan->write_open_refcount) == 0) {
59503a3f90c6SAndrew Thompson 		DPRINTF("(stopping write transfer)\n");
59516f068a43SHans Petter Selasky 		usbd_transfer_stop(chan->xfer[UMIDI_TX_TRANSFER]);
59523a3f90c6SAndrew Thompson 	}
59533a3f90c6SAndrew Thompson }
59543a3f90c6SAndrew Thompson 
59553a3f90c6SAndrew Thompson static int
umidi_open(struct usb_fifo * fifo,int fflags)5956760bc48eSAndrew Thompson umidi_open(struct usb_fifo *fifo, int fflags)
59573a3f90c6SAndrew Thompson {
5958ed6d949aSAndrew Thompson 	struct umidi_chan *chan = usb_fifo_softc(fifo);
59593a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub = umidi_sub_by_fifo(fifo);
59603a3f90c6SAndrew Thompson 
59613a3f90c6SAndrew Thompson 	if (fflags & FREAD) {
5962a593f6b8SAndrew Thompson 		if (usb_fifo_alloc_buffer(fifo, 4, (1024 / 4))) {
59633a3f90c6SAndrew Thompson 			return (ENOMEM);
59643a3f90c6SAndrew Thompson 		}
59657e6e6b67SAndrew Thompson 		mtx_lock(&chan->mtx);
59663a3f90c6SAndrew Thompson 		chan->read_open_refcount++;
59673a3f90c6SAndrew Thompson 		sub->read_open = 1;
59687e6e6b67SAndrew Thompson 		mtx_unlock(&chan->mtx);
59693a3f90c6SAndrew Thompson 	}
59703a3f90c6SAndrew Thompson 	if (fflags & FWRITE) {
5971a593f6b8SAndrew Thompson 		if (usb_fifo_alloc_buffer(fifo, 32, (1024 / 32))) {
59723a3f90c6SAndrew Thompson 			return (ENOMEM);
59733a3f90c6SAndrew Thompson 		}
59743a3f90c6SAndrew Thompson 		/* clear stall first */
59757e6e6b67SAndrew Thompson 		mtx_lock(&chan->mtx);
59763a3f90c6SAndrew Thompson 		chan->write_open_refcount++;
59773a3f90c6SAndrew Thompson 		sub->write_open = 1;
59783a3f90c6SAndrew Thompson 
59793a3f90c6SAndrew Thompson 		/* reset */
59803a3f90c6SAndrew Thompson 		sub->state = UMIDI_ST_UNKNOWN;
59817e6e6b67SAndrew Thompson 		mtx_unlock(&chan->mtx);
59823a3f90c6SAndrew Thompson 	}
59833a3f90c6SAndrew Thompson 	return (0);			/* success */
59843a3f90c6SAndrew Thompson }
59853a3f90c6SAndrew Thompson 
59863a3f90c6SAndrew Thompson static void
umidi_close(struct usb_fifo * fifo,int fflags)5987760bc48eSAndrew Thompson umidi_close(struct usb_fifo *fifo, int fflags)
59883a3f90c6SAndrew Thompson {
59893a3f90c6SAndrew Thompson 	if (fflags & FREAD) {
5990a593f6b8SAndrew Thompson 		usb_fifo_free_buffer(fifo);
59913a3f90c6SAndrew Thompson 	}
59923a3f90c6SAndrew Thompson 	if (fflags & FWRITE) {
5993a593f6b8SAndrew Thompson 		usb_fifo_free_buffer(fifo);
59943a3f90c6SAndrew Thompson 	}
59953a3f90c6SAndrew Thompson }
59963a3f90c6SAndrew Thompson 
59973a3f90c6SAndrew Thompson static int
umidi_ioctl(struct usb_fifo * fifo,u_long cmd,void * data,int fflags)5998760bc48eSAndrew Thompson umidi_ioctl(struct usb_fifo *fifo, u_long cmd, void *data,
5999ee3e3ff5SAndrew Thompson     int fflags)
60003a3f90c6SAndrew Thompson {
60013a3f90c6SAndrew Thompson 	return (ENODEV);
60023a3f90c6SAndrew Thompson }
60033a3f90c6SAndrew Thompson 
60043a3f90c6SAndrew Thompson static void
umidi_init(device_t dev)60053a3f90c6SAndrew Thompson umidi_init(device_t dev)
60063a3f90c6SAndrew Thompson {
60073a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(dev);
60083a3f90c6SAndrew Thompson 	struct umidi_chan *chan = &sc->sc_midi_chan;
60093a3f90c6SAndrew Thompson 
60103a3f90c6SAndrew Thompson 	mtx_init(&chan->mtx, "umidi lock", NULL, MTX_DEF | MTX_RECURSE);
60113a3f90c6SAndrew Thompson }
60123a3f90c6SAndrew Thompson 
6013760bc48eSAndrew Thompson static struct usb_fifo_methods umidi_fifo_methods = {
60143a3f90c6SAndrew Thompson 	.f_start_read = &umidi_start_read,
60153a3f90c6SAndrew Thompson 	.f_start_write = &umidi_start_write,
60163a3f90c6SAndrew Thompson 	.f_stop_read = &umidi_stop_read,
60173a3f90c6SAndrew Thompson 	.f_stop_write = &umidi_stop_write,
60183a3f90c6SAndrew Thompson 	.f_open = &umidi_open,
60193a3f90c6SAndrew Thompson 	.f_close = &umidi_close,
60203a3f90c6SAndrew Thompson 	.f_ioctl = &umidi_ioctl,
60213a3f90c6SAndrew Thompson 	.basename[0] = "umidi",
60223a3f90c6SAndrew Thompson };
60233a3f90c6SAndrew Thompson 
602425b74dabSHans Petter Selasky static int
umidi_probe(device_t dev)60253a3f90c6SAndrew Thompson umidi_probe(device_t dev)
60263a3f90c6SAndrew Thompson {
60273a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(dev);
6028760bc48eSAndrew Thompson 	struct usb_attach_arg *uaa = device_get_ivars(dev);
60293a3f90c6SAndrew Thompson 	struct umidi_chan *chan = &sc->sc_midi_chan;
60303a3f90c6SAndrew Thompson 	struct umidi_sub_chan *sub;
60313a3f90c6SAndrew Thompson 	int unit = device_get_unit(dev);
60323a3f90c6SAndrew Thompson 	int error;
60333a3f90c6SAndrew Thompson 	uint32_t n;
60343a3f90c6SAndrew Thompson 
60354944c3a8SHans Petter Selasky 	if (usb_test_quirk(uaa, UQ_SINGLE_CMD_MIDI))
60364944c3a8SHans Petter Selasky 		chan->single_command = 1;
60374944c3a8SHans Petter Selasky 
603831070b5bSHans Petter Selasky 	error = usbd_set_alt_interface_index(sc->sc_udev,
603931070b5bSHans Petter Selasky 	    chan->iface_index, chan->iface_alt_index);
604031070b5bSHans Petter Selasky 	if (error) {
604131070b5bSHans Petter Selasky 		DPRINTF("setting of alternate index failed: %s\n",
604231070b5bSHans Petter Selasky 		    usbd_errstr(error));
60433a3f90c6SAndrew Thompson 		goto detach;
60443a3f90c6SAndrew Thompson 	}
60456f068a43SHans Petter Selasky 	usbd_set_parent_iface(sc->sc_udev, chan->iface_index,
60466f068a43SHans Petter Selasky 	    sc->sc_mixer_iface_index);
60473a3f90c6SAndrew Thompson 
6048a593f6b8SAndrew Thompson 	error = usbd_transfer_setup(uaa->device, &chan->iface_index,
60493a3f90c6SAndrew Thompson 	    chan->xfer, umidi_config, UMIDI_N_TRANSFER,
60503a3f90c6SAndrew Thompson 	    chan, &chan->mtx);
60513a3f90c6SAndrew Thompson 	if (error) {
6052a593f6b8SAndrew Thompson 		DPRINTF("error=%s\n", usbd_errstr(error));
60533a3f90c6SAndrew Thompson 		goto detach;
60543a3f90c6SAndrew Thompson 	}
6055d56d9e27SHans Petter Selasky 	if (chan->xfer[UMIDI_TX_TRANSFER] == NULL &&
6056d56d9e27SHans Petter Selasky 	    chan->xfer[UMIDI_RX_TRANSFER] == NULL) {
6057d56d9e27SHans Petter Selasky 		DPRINTF("no BULK or INTERRUPT MIDI endpoint(s) found\n");
6058d56d9e27SHans Petter Selasky 		goto detach;
6059d56d9e27SHans Petter Selasky 	}
6060e91fe3a9SHans Petter Selasky 
6061e91fe3a9SHans Petter Selasky 	/*
6062e91fe3a9SHans Petter Selasky 	 * Some USB MIDI device makers couldn't resist using
6063e91fe3a9SHans Petter Selasky 	 * wMaxPacketSize = 4 for RX and TX BULK endpoints, although
6064e91fe3a9SHans Petter Selasky 	 * that size is an unsupported value for FULL speed BULK
6065e91fe3a9SHans Petter Selasky 	 * endpoints. The same applies to some HIGH speed MIDI devices
6066e91fe3a9SHans Petter Selasky 	 * which are using a wMaxPacketSize different from 512 bytes.
6067e91fe3a9SHans Petter Selasky 	 *
6068e91fe3a9SHans Petter Selasky 	 * Refer to section 5.8.3 in USB 2.0 PDF: Cite: "All Host
6069e91fe3a9SHans Petter Selasky 	 * Controllers are required to have support for 8-, 16-, 32-,
6070e91fe3a9SHans Petter Selasky 	 * and 64-byte maximum packet sizes for full-speed bulk
6071e91fe3a9SHans Petter Selasky 	 * endpoints and 512 bytes for high-speed bulk endpoints."
6072e91fe3a9SHans Petter Selasky 	 */
6073d56d9e27SHans Petter Selasky 	if (chan->xfer[UMIDI_TX_TRANSFER] != NULL &&
6074d56d9e27SHans Petter Selasky 	    usbd_xfer_maxp_was_clamped(chan->xfer[UMIDI_TX_TRANSFER]))
6075e91fe3a9SHans Petter Selasky 		chan->single_command = 1;
6076e91fe3a9SHans Petter Selasky 
6077e91fe3a9SHans Petter Selasky 	if (chan->single_command != 0)
6078e91fe3a9SHans Petter Selasky 		device_printf(dev, "Single command MIDI quirk enabled\n");
6079e91fe3a9SHans Petter Selasky 
60808bf51ab5SHans Petter Selasky 	if ((chan->max_emb_jack == 0) ||
60818bf51ab5SHans Petter Selasky 	    (chan->max_emb_jack > UMIDI_EMB_JACK_MAX)) {
60828bf51ab5SHans Petter Selasky 		chan->max_emb_jack = UMIDI_EMB_JACK_MAX;
60833a3f90c6SAndrew Thompson 	}
60843a3f90c6SAndrew Thompson 
60858bf51ab5SHans Petter Selasky 	for (n = 0; n < chan->max_emb_jack; n++) {
60863a3f90c6SAndrew Thompson 		sub = &chan->sub[n];
60873a3f90c6SAndrew Thompson 
6088a593f6b8SAndrew Thompson 		error = usb_fifo_attach(sc->sc_udev, chan, &chan->mtx,
60893a3f90c6SAndrew Thompson 		    &umidi_fifo_methods, &sub->fifo, unit, n,
6090ee3e3ff5SAndrew Thompson 		    chan->iface_index,
60912fcd7d38SHans Petter Selasky 		    UID_ROOT, GID_OPERATOR, 0666);
60923a3f90c6SAndrew Thompson 		if (error) {
60933a3f90c6SAndrew Thompson 			goto detach;
60943a3f90c6SAndrew Thompson 		}
60953a3f90c6SAndrew Thompson 	}
60963a3f90c6SAndrew Thompson 
60973a3f90c6SAndrew Thompson 	mtx_lock(&chan->mtx);
60983a3f90c6SAndrew Thompson 
60993a3f90c6SAndrew Thompson 	/*
61006f068a43SHans Petter Selasky 	 * NOTE: At least one device will not work properly unless the
61016f068a43SHans Petter Selasky 	 * BULK IN pipe is open all the time. This might have to do
61026f068a43SHans Petter Selasky 	 * about that the internal queues of the device overflow if we
61036f068a43SHans Petter Selasky 	 * don't read them regularly.
61043a3f90c6SAndrew Thompson 	 */
61056f068a43SHans Petter Selasky 	usbd_transfer_start(chan->xfer[UMIDI_RX_TRANSFER]);
61063a3f90c6SAndrew Thompson 
61073a3f90c6SAndrew Thompson 	mtx_unlock(&chan->mtx);
61083a3f90c6SAndrew Thompson 
61093a3f90c6SAndrew Thompson 	return (0);			/* success */
61103a3f90c6SAndrew Thompson 
61113a3f90c6SAndrew Thompson detach:
61123a3f90c6SAndrew Thompson 	return (ENXIO);			/* failure */
61133a3f90c6SAndrew Thompson }
61143a3f90c6SAndrew Thompson 
611525b74dabSHans Petter Selasky static int
umidi_detach(device_t dev)61163a3f90c6SAndrew Thompson umidi_detach(device_t dev)
61173a3f90c6SAndrew Thompson {
61183a3f90c6SAndrew Thompson 	struct uaudio_softc *sc = device_get_softc(dev);
61193a3f90c6SAndrew Thompson 	struct umidi_chan *chan = &sc->sc_midi_chan;
61203a3f90c6SAndrew Thompson 	uint32_t n;
61213a3f90c6SAndrew Thompson 
61228bf51ab5SHans Petter Selasky 	for (n = 0; n < UMIDI_EMB_JACK_MAX; n++)
6123a593f6b8SAndrew Thompson 		usb_fifo_detach(&chan->sub[n].fifo);
61243a3f90c6SAndrew Thompson 
61253a3f90c6SAndrew Thompson 	mtx_lock(&chan->mtx);
61263a3f90c6SAndrew Thompson 
61276f068a43SHans Petter Selasky 	usbd_transfer_stop(chan->xfer[UMIDI_RX_TRANSFER]);
61283a3f90c6SAndrew Thompson 
61293a3f90c6SAndrew Thompson 	mtx_unlock(&chan->mtx);
61303a3f90c6SAndrew Thompson 
6131a593f6b8SAndrew Thompson 	usbd_transfer_unsetup(chan->xfer, UMIDI_N_TRANSFER);
61323a3f90c6SAndrew Thompson 
61333a3f90c6SAndrew Thompson 	mtx_destroy(&chan->mtx);
61343a3f90c6SAndrew Thompson 
61353a3f90c6SAndrew Thompson 	return (0);
61363a3f90c6SAndrew Thompson }
61373a3f90c6SAndrew Thompson 
613876b71212SHans Petter Selasky static void
uaudio_hid_rx_callback(struct usb_xfer * xfer,usb_error_t error)613976b71212SHans Petter Selasky uaudio_hid_rx_callback(struct usb_xfer *xfer, usb_error_t error)
614076b71212SHans Petter Selasky {
614176b71212SHans Petter Selasky 	struct uaudio_softc *sc = usbd_xfer_softc(xfer);
614276b71212SHans Petter Selasky 	const uint8_t *buffer = usbd_xfer_get_frame_buffer(xfer, 0);
614376b71212SHans Petter Selasky 	struct snd_mixer *m;
614476b71212SHans Petter Selasky 	uint8_t id;
614576b71212SHans Petter Selasky 	int actlen;
614676b71212SHans Petter Selasky 
614776b71212SHans Petter Selasky 	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
614876b71212SHans Petter Selasky 
614976b71212SHans Petter Selasky 	switch (USB_GET_STATE(xfer)) {
615076b71212SHans Petter Selasky 	case USB_ST_TRANSFERRED:
615176b71212SHans Petter Selasky 		DPRINTF("actlen=%d\n", actlen);
615276b71212SHans Petter Selasky 
615376b71212SHans Petter Selasky 		if (actlen != 0 &&
615476b71212SHans Petter Selasky 		    (sc->sc_hid.flags & UAUDIO_HID_HAS_ID)) {
615576b71212SHans Petter Selasky 			id = *buffer;
615676b71212SHans Petter Selasky 			buffer++;
615776b71212SHans Petter Selasky 			actlen--;
615876b71212SHans Petter Selasky 		} else {
615976b71212SHans Petter Selasky 			id = 0;
616076b71212SHans Petter Selasky 		}
616176b71212SHans Petter Selasky 
616286c9b3f3SHans Petter Selasky 		m = sc->sc_child[0].mixer_dev;
616376b71212SHans Petter Selasky 
61642ba0f361SHans Petter Selasky 		if ((sc->sc_hid.flags & UAUDIO_HID_HAS_MUTE) &&
61652ba0f361SHans Petter Selasky 		    (sc->sc_hid.mute_id == id) &&
61662ba0f361SHans Petter Selasky 		    hid_get_data(buffer, actlen,
61672ba0f361SHans Petter Selasky 		    &sc->sc_hid.mute_loc)) {
61682ba0f361SHans Petter Selasky 			DPRINTF("Mute toggle\n");
61692ba0f361SHans Petter Selasky 
61702ba0f361SHans Petter Selasky 			mixer_hwvol_mute_locked(m);
61712ba0f361SHans Petter Selasky 		}
61722ba0f361SHans Petter Selasky 
617376b71212SHans Petter Selasky 		if ((sc->sc_hid.flags & UAUDIO_HID_HAS_VOLUME_UP) &&
617476b71212SHans Petter Selasky 		    (sc->sc_hid.volume_up_id == id) &&
617576b71212SHans Petter Selasky 		    hid_get_data(buffer, actlen,
617676b71212SHans Petter Selasky 		    &sc->sc_hid.volume_up_loc)) {
617776b71212SHans Petter Selasky 			DPRINTF("Volume Up\n");
617876b71212SHans Petter Selasky 
61792ba0f361SHans Petter Selasky 			mixer_hwvol_step_locked(m, 1, 1);
618076b71212SHans Petter Selasky 		}
618176b71212SHans Petter Selasky 
618276b71212SHans Petter Selasky 		if ((sc->sc_hid.flags & UAUDIO_HID_HAS_VOLUME_DOWN) &&
618376b71212SHans Petter Selasky 		    (sc->sc_hid.volume_down_id == id) &&
618476b71212SHans Petter Selasky 		    hid_get_data(buffer, actlen,
618576b71212SHans Petter Selasky 		    &sc->sc_hid.volume_down_loc)) {
618676b71212SHans Petter Selasky 			DPRINTF("Volume Down\n");
618776b71212SHans Petter Selasky 
61882ba0f361SHans Petter Selasky 			mixer_hwvol_step_locked(m, -1, -1);
618976b71212SHans Petter Selasky 		}
619076b71212SHans Petter Selasky 
619176b71212SHans Petter Selasky 	case USB_ST_SETUP:
619276b71212SHans Petter Selasky tr_setup:
619376b71212SHans Petter Selasky 		/* check if we can put more data into the FIFO */
619476b71212SHans Petter Selasky 		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
619576b71212SHans Petter Selasky 		usbd_transfer_submit(xfer);
619676b71212SHans Petter Selasky 		break;
619776b71212SHans Petter Selasky 
619876b71212SHans Petter Selasky 	default:			/* Error */
6199e5359a3bSHans Petter Selasky 
6200e5359a3bSHans Petter Selasky 		DPRINTF("error=%s\n", usbd_errstr(error));
6201e5359a3bSHans Petter Selasky 
620276b71212SHans Petter Selasky 		if (error != USB_ERR_CANCELLED) {
6203e5359a3bSHans Petter Selasky 			/* try to clear stall first */
620476b71212SHans Petter Selasky 			usbd_xfer_set_stall(xfer);
620576b71212SHans Petter Selasky 			goto tr_setup;
620676b71212SHans Petter Selasky 		}
620776b71212SHans Petter Selasky 		break;
620876b71212SHans Petter Selasky 	}
620976b71212SHans Petter Selasky }
621076b71212SHans Petter Selasky 
621176b71212SHans Petter Selasky static int
uaudio_hid_probe(struct uaudio_softc * sc,struct usb_attach_arg * uaa)621276b71212SHans Petter Selasky uaudio_hid_probe(struct uaudio_softc *sc,
621376b71212SHans Petter Selasky     struct usb_attach_arg *uaa)
621476b71212SHans Petter Selasky {
621576b71212SHans Petter Selasky 	void *d_ptr;
621676b71212SHans Petter Selasky 	uint32_t flags;
621776b71212SHans Petter Selasky 	uint16_t d_len;
621876b71212SHans Petter Selasky 	uint8_t id;
621976b71212SHans Petter Selasky 	int error;
622076b71212SHans Petter Selasky 
622176b71212SHans Petter Selasky 	if (!(sc->sc_hid.flags & UAUDIO_HID_VALID))
622276b71212SHans Petter Selasky 		return (-1);
622376b71212SHans Petter Selasky 
622486c9b3f3SHans Petter Selasky 	if (sc->sc_child[0].mixer_lock == NULL)
622576b71212SHans Petter Selasky 		return (-1);
622676b71212SHans Petter Selasky 
622776b71212SHans Petter Selasky 	/* Get HID descriptor */
622876b71212SHans Petter Selasky 	error = usbd_req_get_hid_desc(uaa->device, NULL, &d_ptr,
622976b71212SHans Petter Selasky 	    &d_len, M_TEMP, sc->sc_hid.iface_index);
623076b71212SHans Petter Selasky 
623176b71212SHans Petter Selasky 	if (error) {
623276b71212SHans Petter Selasky 		DPRINTF("error reading report description\n");
623376b71212SHans Petter Selasky 		return (-1);
623476b71212SHans Petter Selasky 	}
623576b71212SHans Petter Selasky 
623676b71212SHans Petter Selasky 	/* check if there is an ID byte */
6237eead9017SVladimir Kondratyev 	hid_report_size_max(d_ptr, d_len, hid_input, &id);
623876b71212SHans Petter Selasky 
623976b71212SHans Petter Selasky 	if (id != 0)
624076b71212SHans Petter Selasky 		sc->sc_hid.flags |= UAUDIO_HID_HAS_ID;
624176b71212SHans Petter Selasky 
624276b71212SHans Petter Selasky 	if (hid_locate(d_ptr, d_len,
624376b71212SHans Petter Selasky 	    HID_USAGE2(HUP_CONSUMER, 0xE9 /* Volume Increment */),
624476b71212SHans Petter Selasky 	    hid_input, 0, &sc->sc_hid.volume_up_loc, &flags,
624576b71212SHans Petter Selasky 	    &sc->sc_hid.volume_up_id)) {
624676b71212SHans Petter Selasky 		if (flags & HIO_VARIABLE)
624776b71212SHans Petter Selasky 			sc->sc_hid.flags |= UAUDIO_HID_HAS_VOLUME_UP;
624876b71212SHans Petter Selasky 		DPRINTFN(1, "Found Volume Up key\n");
624976b71212SHans Petter Selasky 	}
625076b71212SHans Petter Selasky 
625176b71212SHans Petter Selasky 	if (hid_locate(d_ptr, d_len,
625276b71212SHans Petter Selasky 	    HID_USAGE2(HUP_CONSUMER, 0xEA /* Volume Decrement */),
625376b71212SHans Petter Selasky 	    hid_input, 0, &sc->sc_hid.volume_down_loc, &flags,
625476b71212SHans Petter Selasky 	    &sc->sc_hid.volume_down_id)) {
625576b71212SHans Petter Selasky 		if (flags & HIO_VARIABLE)
625676b71212SHans Petter Selasky 			sc->sc_hid.flags |= UAUDIO_HID_HAS_VOLUME_DOWN;
625776b71212SHans Petter Selasky 		DPRINTFN(1, "Found Volume Down key\n");
625876b71212SHans Petter Selasky 	}
625976b71212SHans Petter Selasky 
62602ba0f361SHans Petter Selasky 	if (hid_locate(d_ptr, d_len,
62612ba0f361SHans Petter Selasky 	    HID_USAGE2(HUP_CONSUMER, 0xE2 /* Mute */),
62622ba0f361SHans Petter Selasky 	    hid_input, 0, &sc->sc_hid.mute_loc, &flags,
62632ba0f361SHans Petter Selasky 	    &sc->sc_hid.mute_id)) {
62642ba0f361SHans Petter Selasky 		if (flags & HIO_VARIABLE)
62652ba0f361SHans Petter Selasky 			sc->sc_hid.flags |= UAUDIO_HID_HAS_MUTE;
62662ba0f361SHans Petter Selasky 		DPRINTFN(1, "Found Mute key\n");
62672ba0f361SHans Petter Selasky 	}
62682ba0f361SHans Petter Selasky 
626976b71212SHans Petter Selasky 	free(d_ptr, M_TEMP);
627076b71212SHans Petter Selasky 
627176b71212SHans Petter Selasky 	if (!(sc->sc_hid.flags & (UAUDIO_HID_HAS_VOLUME_UP |
62722ba0f361SHans Petter Selasky 	    UAUDIO_HID_HAS_VOLUME_DOWN |
62732ba0f361SHans Petter Selasky 	    UAUDIO_HID_HAS_MUTE))) {
627476b71212SHans Petter Selasky 		DPRINTFN(1, "Did not find any volume related keys\n");
627576b71212SHans Petter Selasky 		return (-1);
627676b71212SHans Petter Selasky 	}
627776b71212SHans Petter Selasky 
627876b71212SHans Petter Selasky 	/* prevent the uhid driver from attaching */
627976b71212SHans Petter Selasky 	usbd_set_parent_iface(uaa->device, sc->sc_hid.iface_index,
628076b71212SHans Petter Selasky 	    sc->sc_mixer_iface_index);
628176b71212SHans Petter Selasky 
628276b71212SHans Petter Selasky 	/* allocate USB transfers */
628376b71212SHans Petter Selasky 	error = usbd_transfer_setup(uaa->device, &sc->sc_hid.iface_index,
628476b71212SHans Petter Selasky 	    sc->sc_hid.xfer, uaudio_hid_config, UAUDIO_HID_N_TRANSFER,
628586c9b3f3SHans Petter Selasky 	    sc, sc->sc_child[0].mixer_lock);
628676b71212SHans Petter Selasky 	if (error) {
628776b71212SHans Petter Selasky 		DPRINTF("error=%s\n", usbd_errstr(error));
628876b71212SHans Petter Selasky 		return (-1);
628976b71212SHans Petter Selasky 	}
629076b71212SHans Petter Selasky 	return (0);
629176b71212SHans Petter Selasky }
629276b71212SHans Petter Selasky 
629376b71212SHans Petter Selasky static void
uaudio_hid_detach(struct uaudio_softc * sc)629476b71212SHans Petter Selasky uaudio_hid_detach(struct uaudio_softc *sc)
629576b71212SHans Petter Selasky {
629676b71212SHans Petter Selasky 	usbd_transfer_unsetup(sc->sc_hid.xfer, UAUDIO_HID_N_TRANSFER);
629776b71212SHans Petter Selasky }
629876b71212SHans Petter Selasky 
629906a43763SChristos Margiolis DRIVER_MODULE_ORDERED(snd_uaudio, uhub, uaudio_driver, NULL, NULL, SI_ORDER_ANY);
630006a43763SChristos Margiolis MODULE_DEPEND(snd_uaudio, usb, 1, 1, 1);
630106a43763SChristos Margiolis MODULE_DEPEND(snd_uaudio, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
630206a43763SChristos Margiolis MODULE_DEPEND(snd_uaudio, hid, 1, 1, 1);
630306a43763SChristos Margiolis MODULE_VERSION(snd_uaudio, 1);
6304f809f280SWarner Losh USB_PNP_HOST_INFO(uaudio_devs);
63059de921eeSHans Petter Selasky USB_PNP_HOST_INFO(uaudio_vendor_midi);
6306