[alsa-devel] [PATCH 1/9] ALSA: usb-audio: UAC2: clean up parsing of bmaControls
Introduce two new static inline functions for a more readable parsing of UAC2 bmaControls.
Signed-off-by: Daniel Mack daniel@caiaq.de --- include/linux/usb/audio-v2.h | 10 ++++++++++ sound/usb/mixer.c | 8 ++++---- 2 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/include/linux/usb/audio-v2.h b/include/linux/usb/audio-v2.h index 92f1d99..8f91be3 100644 --- a/include/linux/usb/audio-v2.h +++ b/include/linux/usb/audio-v2.h @@ -18,6 +18,16 @@ /* v1.0 and v2.0 of this standard have many things in common. For the rest * of the definitions, please refer to audio.h */
+static inline bool uac2_control_is_readable(u32 bmControls, u8 control) +{ + return (bmControls >> (control * 2)) & 0x1; +} + +static inline bool uac2_control_is_writeable(u32 bmControls, u8 control) +{ + return (bmControls >> (control * 2)) & 0x2; +} + /* 4.7.2.1 Clock Source Descriptor */
struct uac_clock_source_descriptor { diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 03ce971..43d6417 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1188,9 +1188,9 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void
for (j = 0; j < channels; j++) { unsigned int mask = snd_usb_combine_bytes(bmaControls + csize * (j+1), csize); - if (mask & (1 << (i * 2))) { + if (uac2_control_is_readable(mask, i)) { ch_bits |= (1 << j); - if (~mask & (1 << ((i * 2) + 1))) + if (!uac2_control_is_writeable(mask, i)) ch_read_only |= (1 << j); } } @@ -1198,9 +1198,9 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void /* FIXME: the whole unit is read-only if any of the channels is marked read-only */ if (ch_bits & 1) /* the first channel must be set (for ease of programming) */ build_feature_ctl(state, _ftr, ch_bits, i, &iterm, unitid, !!ch_read_only); - if (master_bits & (1 << i * 2)) + if (uac2_control_is_readable(master_bits, i)) build_feature_ctl(state, _ftr, 0, i, &iterm, unitid, - ~master_bits & (1 << ((i * 2) + 1))); + !uac2_control_is_writeable(master_bits, i)); } }
So far, UAC2 controls are marked read-only if any of the channels are marked read-only in the descriptors. Change this behaviour and
- mark them writeable unless all channels are read-only - store the read-only mask in usb_mixer_elem_info and - check the mask again in set_cur_mix_value(), and bail out for write-protected channels.
Signed-off-by: Daniel Mack daniel@caiaq.de --- sound/usb/mixer.c | 30 ++++++++++++++++++++++++------ sound/usb/mixer.h | 2 ++ 2 files changed, 26 insertions(+), 6 deletions(-)
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 43d6417..9149a84 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -462,6 +462,16 @@ static int set_cur_mix_value(struct usb_mixer_elem_info *cval, int channel, int index, int value) { int err; + unsigned int read_only = (channel == 0) ? + cval->master_readonly : + cval->ch_readonly & (1 << (channel - 1)); + + if (read_only) { + snd_printdd(KERN_INFO "%s(): channel %d of control %d is read_only\n", + __func__, channel, cval->control); + return 0; + } + err = snd_usb_mixer_set_ctl_value(cval, UAC_SET_CUR, (cval->control << 8) | channel, value); if (err < 0) @@ -958,7 +968,7 @@ static size_t append_ctl_name(struct snd_kcontrol *kctl, const char *str) static void build_feature_ctl(struct mixer_build *state, void *raw_desc, unsigned int ctl_mask, int control, struct usb_audio_term *iterm, int unitid, - int read_only) + int readonly_mask) { struct uac_feature_unit_descriptor *desc = raw_desc; unsigned int len = 0; @@ -989,20 +999,25 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, cval->control = control; cval->cmask = ctl_mask; cval->val_type = audio_feature_info[control-1].type; - if (ctl_mask == 0) + if (ctl_mask == 0) { cval->channels = 1; /* master channel */ - else { + cval->master_readonly = readonly_mask; + } else { int i, c = 0; for (i = 0; i < 16; i++) if (ctl_mask & (1 << i)) c++; cval->channels = c; + cval->ch_readonly = readonly_mask; }
/* get min/max values */ get_min_max(cval, 0);
- if (read_only) + /* if all channels in the mask are marked read-only, make the control + * read-only. set_cur_mix_value() will check the mask again and won't + * issue write commands to read-only channels. */ + if (cval->channels == readonly_mask) kctl = snd_ctl_new1(&usb_feature_unit_ctl_ro, cval); else kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval); @@ -1195,9 +1210,12 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void } }
- /* FIXME: the whole unit is read-only if any of the channels is marked read-only */ + /* NOTE: build_feature_ctl() will mark the control read-only if all channels + * are marked read-only in the descriptors. Otherwise, the control will be + * reported as writeable, but the driver will not actually issue a write + * command for read-only channels */ if (ch_bits & 1) /* the first channel must be set (for ease of programming) */ - build_feature_ctl(state, _ftr, ch_bits, i, &iterm, unitid, !!ch_read_only); + build_feature_ctl(state, _ftr, ch_bits, i, &iterm, unitid, ch_read_only); if (uac2_control_is_readable(master_bits, i)) build_feature_ctl(state, _ftr, 0, i, &iterm, unitid, !uac2_control_is_writeable(master_bits, i)); diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 1301238..a7cf100 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -34,6 +34,8 @@ struct usb_mixer_elem_info { unsigned int id; unsigned int control; /* CS or ICN (high byte) */ unsigned int cmask; /* channel mask bitmap: 0 = master */ + unsigned int ch_readonly; + unsigned int master_readonly; int channels; int val_type; int min, max, res;
Also, remove the 'bmControl' field from uac_clock_selector_descriptor, which was at the wrong offset. This struct is currently unused.
Signed-off-by: Daniel Mack daniel@caiaq.de --- include/linux/usb/audio-v2.h | 21 ++++++++++++++++++++- 1 files changed, 20 insertions(+), 1 deletions(-)
diff --git a/include/linux/usb/audio-v2.h b/include/linux/usb/audio-v2.h index 8f91be3..383b94b 100644 --- a/include/linux/usb/audio-v2.h +++ b/include/linux/usb/audio-v2.h @@ -41,6 +41,13 @@ struct uac_clock_source_descriptor { __u8 iClockSource; } __attribute__((packed));
+/* bmAttribute fields */ +#define UAC_CLOCK_SOURCE_TYPE_EXT 0x0 +#define UAC_CLOCK_SOURCE_TYPE_INT_FIXED 0x1 +#define UAC_CLOCK_SOURCE_TYPE_INT_VAR 0x2 +#define UAC_CLOCK_SOURCE_TYPE_INT_PROG 0x3 +#define UAC_CLOCK_SOURCE_SYNCED_TO_SOF (1 << 2) + /* 4.7.2.2 Clock Source Descriptor */
struct uac_clock_selector_descriptor { @@ -49,8 +56,20 @@ struct uac_clock_selector_descriptor { __u8 bDescriptorSubtype; __u8 bClockID; __u8 bNrInPins; - __u8 bmControls; __u8 baCSourceID[]; + /* bmControls, bAssocTerminal and iClockSource omitted */ +} __attribute__((packed)); + +/* 4.7.2.3 Clock Multiplier Descriptor */ + +struct uac_clock_multiplier_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u8 bDescriptorSubtype; + __u8 bClockID; + __u8 bCSourceID; + __u8 bmControls; + __u8 iClockMultiplier; } __attribute__((packed));
/* 4.7.2.4 Input terminal descriptor */
This is another regression from the UAC2 code refactoring.
Signed-off-by: Daniel Mack daniel@caiaq.de --- include/linux/usb/audio.h | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/include/linux/usb/audio.h b/include/linux/usb/audio.h index 5d646c3..ed5cf92 100644 --- a/include/linux/usb/audio.h +++ b/include/linux/usb/audio.h @@ -244,7 +244,7 @@ struct uac_selector_unit_descriptor { static inline __u8 uac_selector_unit_iSelector(struct uac_selector_unit_descriptor *desc) { __u8 *raw = (__u8 *) desc; - return raw[9 + desc->bLength - 1]; + return raw[desc->bLength - 1]; }
/* 4.3.2.5 Feature Unit Descriptor */
Audio devices which comply to the UAC2 standard can export complex clock topologies in its descriptors and set up links between them.
The entities that are defined are
- clock sources, which define the end-leafs. - clock selectors, which act as switch to select one out of many possible clocks sources. - clock multipliers, which have an input clock source, and act as clock source again. They can be used to derive one clock from another.
All sample rate changes, clock validity queries and the like must go to clock source elements, while clock selectors and multipliers can be used as terminal clock source.
The following patch adds a parser for these elements and functions to iterate over the tree and find the leaf nodes (clock sources).
The samplerate set functions were moved to the new clock.c file.
Signed-off-by: Daniel Mack daniel@caiaq.de --- sound/usb/Makefile | 3 +- sound/usb/card.c | 18 +--- sound/usb/card.h | 1 + sound/usb/clock.c | 310 ++++++++++++++++++++++++++++++++++++++++++++++++++ sound/usb/clock.h | 12 ++ sound/usb/endpoint.c | 57 +++++++++- sound/usb/format.c | 16 ++- sound/usb/pcm.c | 98 +---------------- sound/usb/usbaudio.h | 5 +- 9 files changed, 395 insertions(+), 125 deletions(-) create mode 100644 sound/usb/clock.c create mode 100644 sound/usb/clock.h
diff --git a/sound/usb/Makefile b/sound/usb/Makefile index e7ac7f4..1e362bf 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -11,7 +11,8 @@ snd-usb-audio-objs := card.o \ endpoint.o \ urb.o \ pcm.o \ - helper.o + helper.o \ + clock.o
snd-usbmidi-lib-objs := midi.o
diff --git a/sound/usb/card.c b/sound/usb/card.c index da1346b..7a8ac1d 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -236,7 +236,6 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif) }
case UAC_VERSION_2: { - struct uac_clock_source_descriptor *cs; struct usb_interface_assoc_descriptor *assoc = usb_ifnum_to_if(dev, ctrlif)->intf_assoc;
@@ -245,21 +244,6 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif) return -EINVAL; }
- /* FIXME: for now, we expect there is at least one clock source - * descriptor and we always take the first one. - * We should properly support devices with multiple clock sources, - * clock selectors and sample rate conversion units. */ - - cs = snd_usb_find_csint_desc(host_iface->extra, host_iface->extralen, - NULL, UAC2_CLOCK_SOURCE); - - if (!cs) { - snd_printk(KERN_ERR "CLOCK_SOURCE descriptor not found\n"); - return -EINVAL; - } - - chip->clock_id = cs->bClockID; - for (i = 0; i < assoc->bInterfaceCount; i++) { int intf = assoc->bFirstInterface + i;
@@ -481,6 +465,8 @@ static void *snd_usb_audio_probe(struct usb_device *dev, goto __error; }
+ chip->ctrl_intf = alts; + if (err > 0) { /* create normal USB audio interfaces */ if (snd_usb_create_streams(chip, ifnum) < 0 || diff --git a/sound/usb/card.h b/sound/usb/card.h index ed92420..1febf2f 100644 --- a/sound/usb/card.h +++ b/sound/usb/card.h @@ -25,6 +25,7 @@ struct audioformat { unsigned int rate_min, rate_max; /* min/max rates */ unsigned int nr_rates; /* number of rate table entries */ unsigned int *rate_table; /* rate table */ + unsigned char clock; /* associated clock */ };
struct snd_usb_substream; diff --git a/sound/usb/clock.c b/sound/usb/clock.c new file mode 100644 index 0000000..94f2664 --- /dev/null +++ b/sound/usb/clock.c @@ -0,0 +1,310 @@ +/* + * Clock domain and sample rate management functions + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/bitops.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/usb.h> +#include <linux/moduleparam.h> +#include <linux/mutex.h> +#include <linux/usb/audio.h> +#include <linux/usb/audio-v2.h> + +#include <sound/core.h> +#include <sound/info.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> + +#include "usbaudio.h" +#include "card.h" +#include "midi.h" +#include "mixer.h" +#include "proc.h" +#include "quirks.h" +#include "endpoint.h" +#include "helper.h" +#include "debug.h" +#include "pcm.h" +#include "urb.h" +#include "format.h" + +static struct uac_clock_source_descriptor * + snd_usb_find_clock_source(struct usb_host_interface *ctrl_iface, + int clock_id) +{ + struct uac_clock_source_descriptor *cs = NULL; + + while ((cs = snd_usb_find_csint_desc(ctrl_iface->extra, + ctrl_iface->extralen, + cs, UAC2_CLOCK_SOURCE))) { + if (cs->bClockID == clock_id) + return cs; + } + + return NULL; +} + +static struct uac_clock_selector_descriptor * + snd_usb_find_clock_selector(struct usb_host_interface *ctrl_iface, + int clock_id) +{ + struct uac_clock_selector_descriptor *cs = NULL; + + while ((cs = snd_usb_find_csint_desc(ctrl_iface->extra, + ctrl_iface->extralen, + cs, UAC2_CLOCK_SELECTOR))) { + if (cs->bClockID == clock_id) + return cs; + } + + return NULL; +} + +static struct uac_clock_multiplier_descriptor * + snd_usb_find_clock_multiplier(struct usb_host_interface *ctrl_iface, + int clock_id) +{ + struct uac_clock_multiplier_descriptor *cs = NULL; + + while ((cs = snd_usb_find_csint_desc(ctrl_iface->extra, + ctrl_iface->extralen, + cs, UAC2_CLOCK_MULTIPLIER))) { + if (cs->bClockID == clock_id) + return cs; + } + + return NULL; +} + +static int uac_clock_selector_get_val(struct snd_usb_audio *chip, int selector_id) +{ + unsigned char buf; + int ret; + + ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), + UAC2_CS_CUR, + USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN, + UAC2_CX_CLOCK_SELECTOR << 8, selector_id << 8, + &buf, sizeof(buf), 1000); + + if (ret < 0) + return ret; + + return buf; +} + +static bool uac_clock_source_is_valid(struct snd_usb_audio *chip, int source_id) +{ + int err; + unsigned char data; + struct usb_device *dev = chip->dev; + + err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_CUR, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + UAC2_CS_CONTROL_CLOCK_VALID << 8, source_id << 8, + &data, sizeof(data), 1000); + + if (err < 0) { + snd_printk(KERN_WARNING "%s(): cannot get clock validity for id %d\n", + __func__, source_id); + return err; + } + + return !!data; +} + +/* Try to find the clock source ID of a given clock entity */ + +static int __uac_clock_find_source(struct snd_usb_audio *chip, + struct usb_host_interface *host_iface, + int entity_id, unsigned long *visited) +{ + struct uac_clock_source_descriptor *source; + struct uac_clock_selector_descriptor *selector; + struct uac_clock_multiplier_descriptor *multiplier; + + entity_id &= 0xff; + + if (test_and_set_bit(entity_id, visited)) { + snd_printk(KERN_WARNING + "%s(): recursive clock topology detected, id %d.\n", + __func__, entity_id); + return -EINVAL; + } + + /* first, see if the ID we're looking for is a clock source already */ + source = snd_usb_find_clock_source(host_iface, entity_id); + if (source) + return source->bClockID; + + selector = snd_usb_find_clock_selector(host_iface, entity_id); + if (selector) { + int ret; + + /* the entity ID we are looking for is a selector. + * find out what it currently selects */ + ret = uac_clock_selector_get_val(chip, selector->bClockID); + if (ret < 0) + return ret; + + if (ret >= selector->bNrInPins) { + printk(KERN_ERR + "%s(): selector reported illegal value, id %d, ret %d\n", + __func__, selector->bClockID, ret); + return -EINVAL; + } + + return __uac_clock_find_source(chip, host_iface, + selector->baCSourceID[ret], + visited); + } + + /* FIXME: multipliers only act as pass-thru element for now */ + multiplier = snd_usb_find_clock_multiplier(host_iface, entity_id); + if (multiplier) + return __uac_clock_find_source(chip, host_iface, + multiplier->bCSourceID, visited); + + return -EINVAL; +} + +int snd_usb_clock_find_source(struct snd_usb_audio *chip, + struct usb_host_interface *host_iface, + int entity_id) +{ + DECLARE_BITMAP(visited, 256); + memset(visited, 0, sizeof(visited)); + return __uac_clock_find_source(chip, host_iface, entity_id, visited); +} + +static int set_sample_rate_v1(struct snd_usb_audio *chip, int iface, + struct usb_host_interface *alts, + struct audioformat *fmt, int rate) +{ + struct usb_device *dev = chip->dev; + unsigned int ep; + unsigned char data[3]; + int err, crate; + + ep = get_endpoint(alts, 0)->bEndpointAddress; + + /* if endpoint doesn't have sampling rate control, bail out */ + if (!(fmt->attributes & UAC_EP_CS_ATTR_SAMPLE_RATE)) { + snd_printk(KERN_WARNING "%d:%d:%d: endpoint lacks sample rate attribute bit, cannot set.\n", + dev->devnum, iface, fmt->altsetting); + return 0; + } + + data[0] = rate; + data[1] = rate >> 8; + data[2] = rate >> 16; + if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, + USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_OUT, + UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, + data, sizeof(data), 1000)) < 0) { + snd_printk(KERN_ERR "%d:%d:%d: cannot set freq %d to ep %#x\n", + dev->devnum, iface, fmt->altsetting, rate, ep); + return err; + } + + if ((err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC_GET_CUR, + USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_IN, + UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, + data, sizeof(data), 1000)) < 0) { + snd_printk(KERN_WARNING "%d:%d:%d: cannot get freq at ep %#x\n", + dev->devnum, iface, fmt->altsetting, ep); + return 0; /* some devices don't support reading */ + } + + crate = data[0] | (data[1] << 8) | (data[2] << 16); + if (crate != rate) { + snd_printd(KERN_WARNING "current rate %d is different from the runtime rate %d\n", crate, rate); + // runtime->rate = crate; + } + + return 0; +} + +static int set_sample_rate_v2(struct snd_usb_audio *chip, int iface, + struct usb_host_interface *alts, + struct audioformat *fmt, int rate) +{ + struct usb_device *dev = chip->dev; + unsigned char data[4]; + int err, crate; + int clock = snd_usb_clock_find_source(chip, chip->ctrl_intf, fmt->clock); + + if (clock < 0) + return clock; + + if (!uac_clock_source_is_valid(chip, clock)) { + snd_printk(KERN_ERR "%d:%d:%d: clock source %d is not valid, cannot use\n", + dev->devnum, iface, fmt->altsetting, clock); + return -ENXIO; + } + + data[0] = rate; + data[1] = rate >> 8; + data[2] = rate >> 16; + data[3] = rate >> 24; + if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC2_CS_CUR, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, + UAC2_CS_CONTROL_SAM_FREQ << 8, clock << 8, + data, sizeof(data), 1000)) < 0) { + snd_printk(KERN_ERR "%d:%d:%d: cannot set freq %d (v2)\n", + dev->devnum, iface, fmt->altsetting, rate); + return err; + } + + if ((err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_CUR, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + UAC2_CS_CONTROL_SAM_FREQ << 8, clock << 8, + data, sizeof(data), 1000)) < 0) { + snd_printk(KERN_WARNING "%d:%d:%d: cannot get freq (v2)\n", + dev->devnum, iface, fmt->altsetting); + return err; + } + + crate = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); + if (crate != rate) + snd_printd(KERN_WARNING "current rate %d is different from the runtime rate %d\n", crate, rate); + + return 0; +} + +int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface, + struct usb_host_interface *alts, + struct audioformat *fmt, int rate) +{ + struct usb_interface_descriptor *altsd = get_iface_desc(alts); + + switch (altsd->bInterfaceProtocol) { + case UAC_VERSION_1: + return set_sample_rate_v1(chip, iface, alts, fmt, rate); + + case UAC_VERSION_2: + return set_sample_rate_v2(chip, iface, alts, fmt, rate); + } + + return -EINVAL; +} + diff --git a/sound/usb/clock.h b/sound/usb/clock.h new file mode 100644 index 0000000..beb2536 --- /dev/null +++ b/sound/usb/clock.h @@ -0,0 +1,12 @@ +#ifndef __USBAUDIO_CLOCK_H +#define __USBAUDIO_CLOCK_H + +int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface, + struct usb_host_interface *alts, + struct audioformat *fmt, int rate); + +int snd_usb_clock_find_source(struct snd_usb_audio *chip, + struct usb_host_interface *host_iface, + int entity_id); + +#endif /* __USBAUDIO_CLOCK_H */ diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c index 28ee1ce..9593b91 100644 --- a/sound/usb/endpoint.c +++ b/sound/usb/endpoint.c @@ -190,6 +190,38 @@ static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip, return attributes; }
+static struct uac2_input_terminal_descriptor * + snd_usb_find_input_terminal_descriptor(struct usb_host_interface *ctrl_iface, + int terminal_id) +{ + struct uac2_input_terminal_descriptor *term = NULL; + + while ((term = snd_usb_find_csint_desc(ctrl_iface->extra, + ctrl_iface->extralen, + term, UAC_INPUT_TERMINAL))) { + if (term->bTerminalID == terminal_id) + return term; + } + + return NULL; +} + +static struct uac2_output_terminal_descriptor * + snd_usb_find_output_terminal_descriptor(struct usb_host_interface *ctrl_iface, + int terminal_id) +{ + struct uac2_output_terminal_descriptor *term = NULL; + + while ((term = snd_usb_find_csint_desc(ctrl_iface->extra, + ctrl_iface->extralen, + term, UAC_OUTPUT_TERMINAL))) { + if (term->bTerminalID == terminal_id) + return term; + } + + return NULL; +} + int snd_usb_parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no) { struct usb_device *dev; @@ -199,7 +231,7 @@ int snd_usb_parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no) int i, altno, err, stream; int format = 0, num_channels = 0; struct audioformat *fp = NULL; - int num, protocol; + int num, protocol, clock = 0; struct uac_format_type_i_continuous_descriptor *fmt;
dev = chip->dev; @@ -263,6 +295,8 @@ int snd_usb_parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no) }
case UAC_VERSION_2: { + struct uac2_input_terminal_descriptor *input_term; + struct uac2_output_terminal_descriptor *output_term; struct uac_as_header_descriptor_v2 *as = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL);
@@ -281,7 +315,25 @@ int snd_usb_parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no) num_channels = as->bNrChannels; format = le32_to_cpu(as->bmFormats);
- break; + /* lookup the terminal associated to this interface + * to extract the clock */ + input_term = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf, + as->bTerminalLink); + if (input_term) { + clock = input_term->bCSourceID; + break; + } + + output_term = snd_usb_find_output_terminal_descriptor(chip->ctrl_intf, + as->bTerminalLink); + if (output_term) { + clock = output_term->bCSourceID; + break; + } + + snd_printk(KERN_ERR "%d:%u:%d : bogus bTerminalLink %d\n", + dev->devnum, iface_no, altno, as->bTerminalLink); + continue; }
default: @@ -338,6 +390,7 @@ int snd_usb_parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no) fp->maxpacksize = (((fp->maxpacksize >> 11) & 3) + 1) * (fp->maxpacksize & 0x7ff); fp->attributes = parse_uac_endpoint_attributes(chip, alts, protocol, iface_no); + fp->clock = clock;
/* some quirks for attributes here */
diff --git a/sound/usb/format.c b/sound/usb/format.c index fe29d61..5367cd1 100644 --- a/sound/usb/format.c +++ b/sound/usb/format.c @@ -29,6 +29,7 @@ #include "quirks.h" #include "helper.h" #include "debug.h" +#include "clock.h"
/* * parse the audio format type I descriptor @@ -215,15 +216,17 @@ static int parse_audio_format_rates_v2(struct snd_usb_audio *chip, struct usb_device *dev = chip->dev; unsigned char tmp[2], *data; int i, nr_rates, data_size, ret = 0; + int clock = snd_usb_clock_find_source(chip, chip->ctrl_intf, fp->clock);
/* get the number of sample rates first by only fetching 2 bytes */ ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_RANGE, USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, - UAC2_CS_CONTROL_SAM_FREQ << 8, chip->clock_id << 8, + UAC2_CS_CONTROL_SAM_FREQ << 8, clock << 8, tmp, sizeof(tmp), 1000);
if (ret < 0) { - snd_printk(KERN_ERR "unable to retrieve number of sample rates\n"); + snd_printk(KERN_ERR "%s(): unable to retrieve number of sample rates (clock %d)\n", + __func__, clock); goto err; }
@@ -237,12 +240,13 @@ static int parse_audio_format_rates_v2(struct snd_usb_audio *chip,
/* now get the full information */ ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_RANGE, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, - UAC2_CS_CONTROL_SAM_FREQ << 8, chip->clock_id << 8, - data, data_size, 1000); + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + UAC2_CS_CONTROL_SAM_FREQ << 8, clock << 8, + data, data_size, 1000);
if (ret < 0) { - snd_printk(KERN_ERR "unable to retrieve sample rate range\n"); + snd_printk(KERN_ERR "%s(): unable to retrieve sample rate range (clock %d)\n", + __func__, clock); ret = -EINVAL; goto err_free; } diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c index 056587d..4568298 100644 --- a/sound/usb/pcm.c +++ b/sound/usb/pcm.c @@ -31,6 +31,7 @@ #include "urb.h" #include "helper.h" #include "pcm.h" +#include "clock.h"
/* * return the current pcm pointer. just based on the hwptr_done value. @@ -181,103 +182,6 @@ int snd_usb_init_pitch(struct snd_usb_audio *chip, int iface, return -EINVAL; }
-static int set_sample_rate_v1(struct snd_usb_audio *chip, int iface, - struct usb_host_interface *alts, - struct audioformat *fmt, int rate) -{ - struct usb_device *dev = chip->dev; - unsigned int ep; - unsigned char data[3]; - int err, crate; - - ep = get_endpoint(alts, 0)->bEndpointAddress; - /* if endpoint doesn't have sampling rate control, bail out */ - if (!(fmt->attributes & UAC_EP_CS_ATTR_SAMPLE_RATE)) { - snd_printk(KERN_WARNING "%d:%d:%d: endpoint lacks sample rate attribute bit, cannot set.\n", - dev->devnum, iface, fmt->altsetting); - return 0; - } - - data[0] = rate; - data[1] = rate >> 8; - data[2] = rate >> 16; - if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR, - USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT, - UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, - data, sizeof(data), 1000)) < 0) { - snd_printk(KERN_ERR "%d:%d:%d: cannot set freq %d to ep %#x\n", - dev->devnum, iface, fmt->altsetting, rate, ep); - return err; - } - if ((err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC_GET_CUR, - USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_IN, - UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, - data, sizeof(data), 1000)) < 0) { - snd_printk(KERN_WARNING "%d:%d:%d: cannot get freq at ep %#x\n", - dev->devnum, iface, fmt->altsetting, ep); - return 0; /* some devices don't support reading */ - } - crate = data[0] | (data[1] << 8) | (data[2] << 16); - if (crate != rate) { - snd_printd(KERN_WARNING "current rate %d is different from the runtime rate %d\n", crate, rate); - // runtime->rate = crate; - } - - return 0; -} - -static int set_sample_rate_v2(struct snd_usb_audio *chip, int iface, - struct usb_host_interface *alts, - struct audioformat *fmt, int rate) -{ - struct usb_device *dev = chip->dev; - unsigned char data[4]; - int err, crate; - - data[0] = rate; - data[1] = rate >> 8; - data[2] = rate >> 16; - data[3] = rate >> 24; - if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC2_CS_CUR, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, - UAC2_CS_CONTROL_SAM_FREQ << 8, chip->clock_id << 8, - data, sizeof(data), 1000)) < 0) { - snd_printk(KERN_ERR "%d:%d:%d: cannot set freq %d (v2)\n", - dev->devnum, iface, fmt->altsetting, rate); - return err; - } - if ((err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), UAC2_CS_CUR, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, - UAC2_CS_CONTROL_SAM_FREQ << 8, chip->clock_id << 8, - data, sizeof(data), 1000)) < 0) { - snd_printk(KERN_WARNING "%d:%d:%d: cannot get freq (v2)\n", - dev->devnum, iface, fmt->altsetting); - return err; - } - crate = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); - if (crate != rate) - snd_printd(KERN_WARNING "current rate %d is different from the runtime rate %d\n", crate, rate); - - return 0; -} - -int snd_usb_init_sample_rate(struct snd_usb_audio *chip, int iface, - struct usb_host_interface *alts, - struct audioformat *fmt, int rate) -{ - struct usb_interface_descriptor *altsd = get_iface_desc(alts); - - switch (altsd->bInterfaceProtocol) { - case UAC_VERSION_1: - return set_sample_rate_v1(chip, iface, alts, fmt, rate); - - case UAC_VERSION_2: - return set_sample_rate_v2(chip, iface, alts, fmt, rate); - } - - return -EINVAL; -} - /* * find a matching format and set up the interface */ diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 06ebf24..24d3319 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -40,9 +40,6 @@ struct snd_usb_audio { int num_interfaces; int num_suspended_intf;
- /* for audio class v2 */ - int clock_id; - struct list_head pcm_list; /* list of pcm streams */ int pcm_devs;
@@ -53,6 +50,8 @@ struct snd_usb_audio { int setup; /* from the 'device_setup' module param */ int nrpacks; /* from the 'nrpacks' module param */ int async_unlink; /* from the 'async_unlink' module param */ + + struct usb_host_interface *ctrl_intf; /* the audio control interface */ };
/*
On Mon, May 31, 2010 at 01:35:40PM +0200, Daniel Mack wrote:
Audio devices which comply to the UAC2 standard can export complex clock topologies in its descriptors and set up links between them.
The entities that are defined are
- clock sources, which define the end-leafs.
- clock selectors, which act as switch to select one out of many possible clocks sources.
- clock multipliers, which have an input clock source, and act as clock source again. They can be used to derive one clock from another.
All sample rate changes, clock validity queries and the like must go to clock source elements, while clock selectors and multipliers can be used as terminal clock source.
The following patch adds a parser for these elements and functions to iterate over the tree and find the leaf nodes (clock sources).
The samplerate set functions were moved to the new clock.c file.
Signed-off-by: Daniel Mack daniel@caiaq.de
Sorry, forgot to squash one hunk into this one. New version of the patch is below.
Thanks, Daniel
Move more definitions from private enums to appropriate header files.
Signed-off-by: Daniel Mack daniel@caiaq.de --- include/linux/usb/audio.h | 88 ++++++++++++++++++++++++++++----------- sound/usb/mixer.c | 99 +++++++++++++++------------------------------ sound/usb/mixer_maps.c | 4 +- 3 files changed, 98 insertions(+), 93 deletions(-)
diff --git a/include/linux/usb/audio.h b/include/linux/usb/audio.h index ed5cf92..c51200c 100644 --- a/include/linux/usb/audio.h +++ b/include/linux/usb/audio.h @@ -47,6 +47,15 @@ #define UAC_FORMAT_TYPE 0x02 #define UAC_FORMAT_SPECIFIC 0x03
+/* A.7 Processing Unit Process Types */ +#define UAC_PROCESS_UNDEFINED 0x00 +#define UAC_PROCESS_UP_DOWNMIX 0x01 +#define UAC_PROCESS_DOLBY_PROLOGIC 0x02 +#define UAC_PROCESS_STEREO_EXTENDER 0x03 +#define UAC_PROCESS_REVERB 0x04 +#define UAC_PROCESS_CHORUS 0x05 +#define UAC_PROCESS_DYN_RANGE_COMP 0x06 + /* A.8 Audio Class-Specific Endpoint Descriptor Subtypes */ #define UAC_EP_GENERAL 0x01
@@ -73,6 +82,60 @@
#define UAC_GET_STAT 0xff
+/* A.10 Control Selector Codes */ + +/* A.10.1 Terminal Control Selectors */ +#define UAC_TERM_COPY_PROTECT 0x01 + +/* A.10.2 Feature Unit Control Selectors */ +#define UAC_FU_MUTE 0x01 +#define UAC_FU_VOLUME 0x02 +#define UAC_FU_BASS 0x03 +#define UAC_FU_MID 0x04 +#define UAC_FU_TREBLE 0x05 +#define UAC_FU_GRAPHIC_EQUALIZER 0x06 +#define UAC_FU_AUTOMATIC_GAIN 0x07 +#define UAC_FU_DELAY 0x08 +#define UAC_FU_BASS_BOOST 0x09 +#define UAC_FU_LOUDNESS 0x0a + +#define UAC_CONTROL_BIT(CS) (1 << ((CS) - 1)) + +/* A.10.3.1 Up/Down-mix Processing Unit Controls Selectors */ +#define UAC_UD_ENABLE 0x01 +#define UAC_UD_MODE_SELECT 0x02 + +/* A.10.3.2 Dolby Prologic (tm) Processing Unit Controls Selectors */ +#define UAC_DP_ENABLE 0x01 +#define UAC_DP_MODE_SELECT 0x02 + +/* A.10.3.3 3D Stereo Extender Processing Unit Control Selectors */ +#define UAC_3D_ENABLE 0x01 +#define UAC_3D_SPACE 0x02 + +/* A.10.3.4 Reverberation Processing Unit Control Selectors */ +#define UAC_REVERB_ENABLE 0x01 +#define UAC_REVERB_LEVEL 0x02 +#define UAC_REVERB_TIME 0x03 +#define UAC_REVERB_FEEDBACK 0x04 + +/* A.10.3.5 Chorus Processing Unit Control Selectors */ +#define UAC_CHORUS_ENABLE 0x01 +#define UAC_CHORUS_LEVEL 0x02 +#define UAC_CHORUS_RATE 0x03 +#define UAC_CHORUS_DEPTH 0x04 + +/* A.10.3.6 Dynamic Range Compressor Unit Control Selectors */ +#define UAC_DCR_ENABLE 0x01 +#define UAC_DCR_RATE 0x02 +#define UAC_DCR_MAXAMPL 0x03 +#define UAC_DCR_THRESHOLD 0x04 +#define UAC_DCR_ATTACK_TIME 0x05 +#define UAC_DCR_RELEASE_TIME 0x06 + +/* A.10.4 Extension Unit Control Selectors */ +#define UAC_XU_ENABLE 0x01 + /* MIDI - A.1 MS Class-Specific Interface Descriptor Subtypes */ #define UAC_MS_HEADER 0x01 #define UAC_MIDI_IN_JACK 0x02 @@ -463,31 +526,6 @@ struct uac_iso_endpoint_descriptor { #define UAC_EP_CS_ATTR_PITCH_CONTROL 0x02 #define UAC_EP_CS_ATTR_FILL_MAX 0x80
-/* A.10.2 Feature Unit Control Selectors */ - -#define UAC_FU_CONTROL_UNDEFINED 0x00 -#define UAC_MUTE_CONTROL 0x01 -#define UAC_VOLUME_CONTROL 0x02 -#define UAC_BASS_CONTROL 0x03 -#define UAC_MID_CONTROL 0x04 -#define UAC_TREBLE_CONTROL 0x05 -#define UAC_GRAPHIC_EQUALIZER_CONTROL 0x06 -#define UAC_AUTOMATIC_GAIN_CONTROL 0x07 -#define UAC_DELAY_CONTROL 0x08 -#define UAC_BASS_BOOST_CONTROL 0x09 -#define UAC_LOUDNESS_CONTROL 0x0a - -#define UAC_FU_MUTE (1 << (UAC_MUTE_CONTROL - 1)) -#define UAC_FU_VOLUME (1 << (UAC_VOLUME_CONTROL - 1)) -#define UAC_FU_BASS (1 << (UAC_BASS_CONTROL - 1)) -#define UAC_FU_MID (1 << (UAC_MID_CONTROL - 1)) -#define UAC_FU_TREBLE (1 << (UAC_TREBLE_CONTROL - 1)) -#define UAC_FU_GRAPHIC_EQ (1 << (UAC_GRAPHIC_EQUALIZER_CONTROL - 1)) -#define UAC_FU_AUTO_GAIN (1 << (UAC_AUTOMATIC_GAIN_CONTROL - 1)) -#define UAC_FU_DELAY (1 << (UAC_DELAY_CONTROL - 1)) -#define UAC_FU_BASS_BOOST (1 << (UAC_BASS_BOOST_CONTROL - 1)) -#define UAC_FU_LOUDNESS (1 << (UAC_LOUDNESS_CONTROL - 1)) - /* status word format (3.7.1.1) */
#define UAC1_STATUS_TYPE_ORIG_MASK 0x0f diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 9149a84..2442819 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -78,39 +78,6 @@ enum { USB_MIXER_U16, };
-enum { - USB_PROC_UPDOWN = 1, - USB_PROC_UPDOWN_SWITCH = 1, - USB_PROC_UPDOWN_MODE_SEL = 2, - - USB_PROC_PROLOGIC = 2, - USB_PROC_PROLOGIC_SWITCH = 1, - USB_PROC_PROLOGIC_MODE_SEL = 2, - - USB_PROC_3DENH = 3, - USB_PROC_3DENH_SWITCH = 1, - USB_PROC_3DENH_SPACE = 2, - - USB_PROC_REVERB = 4, - USB_PROC_REVERB_SWITCH = 1, - USB_PROC_REVERB_LEVEL = 2, - USB_PROC_REVERB_TIME = 3, - USB_PROC_REVERB_DELAY = 4, - - USB_PROC_CHORUS = 5, - USB_PROC_CHORUS_SWITCH = 1, - USB_PROC_CHORUS_LEVEL = 2, - USB_PROC_CHORUS_RATE = 3, - USB_PROC_CHORUS_DEPTH = 4, - - USB_PROC_DCR = 6, - USB_PROC_DCR_SWITCH = 1, - USB_PROC_DCR_RATIO = 2, - USB_PROC_DCR_MAX_AMP = 3, - USB_PROC_DCR_THRESHOLD = 4, - USB_PROC_DCR_ATTACK = 5, - USB_PROC_DCR_RELEASE = 6, -};
/*E-mu 0202(0404) eXtension Unit(XU) control*/ enum { @@ -980,7 +947,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc,
control++; /* change from zero-based to 1-based value */
- if (control == UAC_GRAPHIC_EQUALIZER_CONTROL) { + if (control == UAC_FU_GRAPHIC_EQUALIZER) { /* FIXME: not supported yet */ return; } @@ -1036,8 +1003,8 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, kctl->id.name, sizeof(kctl->id.name));
switch (control) { - case UAC_MUTE_CONTROL: - case UAC_VOLUME_CONTROL: + case UAC_FU_MUTE: + case UAC_FU_VOLUME: /* determine the control name. the rule is: * - if a name id is given in descriptor, use it. * - if the connected input can be determined, then use the name @@ -1064,9 +1031,9 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, len = append_ctl_name(kctl, " Playback"); } } - append_ctl_name(kctl, control == UAC_MUTE_CONTROL ? + append_ctl_name(kctl, control == UAC_FU_MUTE ? " Switch" : " Volume"); - if (control == UAC_VOLUME_CONTROL) { + if (control == UAC_FU_VOLUME) { kctl->tlv.c = mixer_vol_tlv; kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ | @@ -1165,7 +1132,7 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void snd_printk(KERN_INFO "usbmixer: master volume quirk for PCM2702 chip\n"); /* disable non-functional volume control */ - master_bits &= ~UAC_FU_VOLUME; + master_bits &= ~UAC_CONTROL_BIT(UAC_FU_VOLUME); break; } if (channels > 0) @@ -1410,51 +1377,51 @@ struct procunit_info { };
static struct procunit_value_info updown_proc_info[] = { - { USB_PROC_UPDOWN_SWITCH, "Switch", USB_MIXER_BOOLEAN }, - { USB_PROC_UPDOWN_MODE_SEL, "Mode Select", USB_MIXER_U8, 1 }, + { UAC_UD_ENABLE, "Switch", USB_MIXER_BOOLEAN }, + { UAC_UD_MODE_SELECT, "Mode Select", USB_MIXER_U8, 1 }, { 0 } }; static struct procunit_value_info prologic_proc_info[] = { - { USB_PROC_PROLOGIC_SWITCH, "Switch", USB_MIXER_BOOLEAN }, - { USB_PROC_PROLOGIC_MODE_SEL, "Mode Select", USB_MIXER_U8, 1 }, + { UAC_DP_ENABLE, "Switch", USB_MIXER_BOOLEAN }, + { UAC_DP_MODE_SELECT, "Mode Select", USB_MIXER_U8, 1 }, { 0 } }; static struct procunit_value_info threed_enh_proc_info[] = { - { USB_PROC_3DENH_SWITCH, "Switch", USB_MIXER_BOOLEAN }, - { USB_PROC_3DENH_SPACE, "Spaciousness", USB_MIXER_U8 }, + { UAC_3D_ENABLE, "Switch", USB_MIXER_BOOLEAN }, + { UAC_3D_SPACE, "Spaciousness", USB_MIXER_U8 }, { 0 } }; static struct procunit_value_info reverb_proc_info[] = { - { USB_PROC_REVERB_SWITCH, "Switch", USB_MIXER_BOOLEAN }, - { USB_PROC_REVERB_LEVEL, "Level", USB_MIXER_U8 }, - { USB_PROC_REVERB_TIME, "Time", USB_MIXER_U16 }, - { USB_PROC_REVERB_DELAY, "Delay", USB_MIXER_U8 }, + { UAC_REVERB_ENABLE, "Switch", USB_MIXER_BOOLEAN }, + { UAC_REVERB_LEVEL, "Level", USB_MIXER_U8 }, + { UAC_REVERB_TIME, "Time", USB_MIXER_U16 }, + { UAC_REVERB_FEEDBACK, "Feedback", USB_MIXER_U8 }, { 0 } }; static struct procunit_value_info chorus_proc_info[] = { - { USB_PROC_CHORUS_SWITCH, "Switch", USB_MIXER_BOOLEAN }, - { USB_PROC_CHORUS_LEVEL, "Level", USB_MIXER_U8 }, - { USB_PROC_CHORUS_RATE, "Rate", USB_MIXER_U16 }, - { USB_PROC_CHORUS_DEPTH, "Depth", USB_MIXER_U16 }, + { UAC_CHORUS_ENABLE, "Switch", USB_MIXER_BOOLEAN }, + { UAC_CHORUS_LEVEL, "Level", USB_MIXER_U8 }, + { UAC_CHORUS_RATE, "Rate", USB_MIXER_U16 }, + { UAC_CHORUS_DEPTH, "Depth", USB_MIXER_U16 }, { 0 } }; static struct procunit_value_info dcr_proc_info[] = { - { USB_PROC_DCR_SWITCH, "Switch", USB_MIXER_BOOLEAN }, - { USB_PROC_DCR_RATIO, "Ratio", USB_MIXER_U16 }, - { USB_PROC_DCR_MAX_AMP, "Max Amp", USB_MIXER_S16 }, - { USB_PROC_DCR_THRESHOLD, "Threshold", USB_MIXER_S16 }, - { USB_PROC_DCR_ATTACK, "Attack Time", USB_MIXER_U16 }, - { USB_PROC_DCR_RELEASE, "Release Time", USB_MIXER_U16 }, + { UAC_DCR_ENABLE, "Switch", USB_MIXER_BOOLEAN }, + { UAC_DCR_RATE, "Ratio", USB_MIXER_U16 }, + { UAC_DCR_MAXAMPL, "Max Amp", USB_MIXER_S16 }, + { UAC_DCR_THRESHOLD, "Threshold", USB_MIXER_S16 }, + { UAC_DCR_ATTACK_TIME, "Attack Time", USB_MIXER_U16 }, + { UAC_DCR_RELEASE_TIME, "Release Time", USB_MIXER_U16 }, { 0 } };
static struct procunit_info procunits[] = { - { USB_PROC_UPDOWN, "Up Down", updown_proc_info }, - { USB_PROC_PROLOGIC, "Dolby Prologic", prologic_proc_info }, - { USB_PROC_3DENH, "3D Stereo Extender", threed_enh_proc_info }, - { USB_PROC_REVERB, "Reverb", reverb_proc_info }, - { USB_PROC_CHORUS, "Chorus", chorus_proc_info }, - { USB_PROC_DCR, "DCR", dcr_proc_info }, + { UAC_PROCESS_UP_DOWNMIX, "Up Down", updown_proc_info }, + { UAC_PROCESS_DOLBY_PROLOGIC, "Dolby Prologic", prologic_proc_info }, + { UAC_PROCESS_STEREO_EXTENDER, "3D Stereo Extender", threed_enh_proc_info }, + { UAC_PROCESS_REVERB, "Reverb", reverb_proc_info }, + { UAC_PROCESS_CHORUS, "Chorus", chorus_proc_info }, + { UAC_PROCESS_DYN_RANGE_COMP, "DCR", dcr_proc_info }, { 0 }, }; /* @@ -1542,7 +1509,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, void *raw cval->channels = 1;
/* get min/max values */ - if (type == USB_PROC_UPDOWN && cval->control == USB_PROC_UPDOWN_MODE_SEL) { + if (type == UAC_PROCESS_UP_DOWNMIX && cval->control == UAC_UD_MODE_SELECT) { __u8 *control_spec = uac_processing_unit_specific(desc, state->mixer->protocol); /* FIXME: hard-coded */ cval->min = 1; diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c index d93fc89..f1324c4 100644 --- a/sound/usb/mixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -85,8 +85,8 @@ static struct usbmix_name_map extigy_map[] = { /* 16: MU (w/o controls) */ { 17, NULL, 1 }, /* DISABLED: PU-switch (any effect?) */ { 17, "Channel Routing", 2 }, /* PU: mode select */ - { 18, "Tone Control - Bass", UAC_BASS_CONTROL }, /* FU */ - { 18, "Tone Control - Treble", UAC_TREBLE_CONTROL }, /* FU */ + { 18, "Tone Control - Bass", UAC_FU_BASS }, /* FU */ + { 18, "Tone Control - Treble", UAC_FU_TREBLE }, /* FU */ { 18, "Master Playback" }, /* FU; others */ /* 19: OT speaker */ /* 20: OT headphone */
The bits to enable them are always 0 for UAC1 devices, so no additional checks are required.
Signed-off-by: Daniel Mack daniel@caiaq.de --- sound/usb/mixer.c | 16 ++++++++++------ 1 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 2442819..8be6bf2 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -686,16 +686,20 @@ struct usb_feature_control_info { };
static struct usb_feature_control_info audio_feature_info[] = { - { "Mute", USB_MIXER_INV_BOOLEAN }, - { "Volume", USB_MIXER_S16 }, + { "Mute", USB_MIXER_INV_BOOLEAN }, + { "Volume", USB_MIXER_S16 }, { "Tone Control - Bass", USB_MIXER_S8 }, { "Tone Control - Mid", USB_MIXER_S8 }, { "Tone Control - Treble", USB_MIXER_S8 }, { "Graphic Equalizer", USB_MIXER_S8 }, /* FIXME: not implemeted yet */ - { "Auto Gain Control", USB_MIXER_BOOLEAN }, - { "Delay Control", USB_MIXER_U16 }, - { "Bass Boost", USB_MIXER_BOOLEAN }, - { "Loudness", USB_MIXER_BOOLEAN }, + { "Auto Gain Control", USB_MIXER_BOOLEAN }, + { "Delay Control", USB_MIXER_U16 }, + { "Bass Boost", USB_MIXER_BOOLEAN }, + { "Loudness", USB_MIXER_BOOLEAN }, + /* UAC2 specific */ + { "Input Gain Control", USB_MIXER_U16 }, + { "Input Gain Pad Control", USB_MIXER_BOOLEAN }, + { "Phase Inverter Control", USB_MIXER_BOOLEAN }, };
Use a struct to parse the audio units, and return usable descriptors for all types. There's no need to limit the result set, except for some kind of sanity check.
Signed-off-by: Daniel Mack daniel@caiaq.de --- sound/usb/mixer.c | 20 +++++++++++--------- 1 files changed, 11 insertions(+), 9 deletions(-)
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 8be6bf2..cb34536 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -165,22 +165,24 @@ static int check_mapped_selector_name(struct mixer_build *state, int unitid,
/* * find an audio control unit with the given unit id - * this doesn't return any clock related units, so they need to be handled elsewhere */ static void *find_audio_control_unit(struct mixer_build *state, unsigned char unit) { - unsigned char *p; + /* we just parse the header */ + struct uac_feature_unit_descriptor *hdr = NULL;
- p = NULL; - while ((p = snd_usb_find_desc(state->buffer, state->buflen, p, - USB_DT_CS_INTERFACE)) != NULL) { - if (p[0] >= 4 && p[2] >= UAC_INPUT_TERMINAL && p[2] <= UAC2_EXTENSION_UNIT_V2 && p[3] == unit) - return p; + while ((hdr = snd_usb_find_desc(state->buffer, state->buflen, hdr, + USB_DT_CS_INTERFACE)) != NULL) { + if (hdr->bLength >= 4 && + hdr->bDescriptorSubtype >= UAC_INPUT_TERMINAL && + hdr->bDescriptorSubtype <= UAC2_SAMPLE_RATE_CONVERTER && + hdr->bUnitID == unit) + return hdr; } + return NULL; }
- /* * copy a string with the given id */
The UAC2 clock selectors are fortunately compatible with UAC1 audio selector units, so we can simply reuse the same approach to get all the linked units.
Requests to this control need a different CS value though.
Signed-off-by: Daniel Mack daniel@caiaq.de --- sound/usb/mixer.c | 42 +++++++++++++++++++++++++++++++++++------- 1 files changed, 35 insertions(+), 7 deletions(-)
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index cb34536..a060d00 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -313,8 +313,8 @@ static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request, int v buf, sizeof(buf), 1000);
if (ret < 0) { - snd_printdd(KERN_ERR "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n", - request, validx, cval->mixer->ctrlif | (cval->id << 8), cval->val_type); + snd_printk(KERN_ERR "cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n", + request, validx, cval->mixer->ctrlif | (cval->id << 8), cval->val_type); return ret; }
@@ -610,6 +610,7 @@ static int get_term_name(struct mixer_build *state, struct usb_audio_term *iterm */ static int check_input_term(struct mixer_build *state, int id, struct usb_audio_term *term) { + int err; void *p1;
memset(term, 0, sizeof(*term)); @@ -630,6 +631,11 @@ static int check_input_term(struct mixer_build *state, int id, struct usb_audio_ term->channels = d->bNrChannels; term->chconfig = le32_to_cpu(d->bmChannelConfig); term->name = d->iTerminal; + + /* call recursively to get the clock selectors */ + err = check_input_term(state, d->bCSourceID, term); + if (err < 0) + return err; } return 0; case UAC_FEATURE_UNIT: { @@ -646,7 +652,8 @@ static int check_input_term(struct mixer_build *state, int id, struct usb_audio_ term->name = uac_mixer_unit_iMixer(d); return 0; } - case UAC_SELECTOR_UNIT: { + case UAC_SELECTOR_UNIT: + case UAC2_CLOCK_SELECTOR: { struct uac_selector_unit_descriptor *d = p1; /* call recursively to retrieve the channel info */ if (check_input_term(state, d->baSourceID[0], term) < 0) @@ -669,6 +676,13 @@ static int check_input_term(struct mixer_build *state, int id, struct usb_audio_ term->name = uac_processing_unit_iProcessing(d, state->mixer->protocol); return 0; } + case UAC2_CLOCK_SOURCE: { + struct uac_clock_source_descriptor *d = p1; + term->type = d->bDescriptorSubtype << 16; /* virtual type */ + term->id = id; + term->name = d->iClockSource; + return 0; + } default: return -ENODEV; } @@ -1610,7 +1624,7 @@ static int mixer_ctl_selector_get(struct snd_kcontrol *kcontrol, struct snd_ctl_ struct usb_mixer_elem_info *cval = kcontrol->private_data; int val, err;
- err = get_cur_ctl_value(cval, 0, &val); + err = get_cur_ctl_value(cval, cval->control << 8, &val); if (err < 0) { if (cval->mixer->ignore_ctl_error) { ucontrol->value.enumerated.item[0] = 0; @@ -1629,7 +1643,7 @@ static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol, struct snd_ctl_ struct usb_mixer_elem_info *cval = kcontrol->private_data; int val, oval, err;
- err = get_cur_ctl_value(cval, 0, &oval); + err = get_cur_ctl_value(cval, cval->control << 8, &oval); if (err < 0) { if (cval->mixer->ignore_ctl_error) return 0; @@ -1638,7 +1652,7 @@ static int mixer_ctl_selector_put(struct snd_kcontrol *kcontrol, struct snd_ctl_ val = ucontrol->value.enumerated.item[0]; val = get_abs_value(cval, val); if (val != oval) { - set_cur_ctl_value(cval, 0, val); + set_cur_ctl_value(cval, cval->control << 8, val); return 1; } return 0; @@ -1720,6 +1734,11 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, void cval->res = 1; cval->initialized = 1;
+ if (desc->bDescriptorSubtype == UAC2_CLOCK_SELECTOR) + cval->control = UAC2_CX_CLOCK_SELECTOR; + else + cval->control = 0; + namelist = kmalloc(sizeof(char *) * desc->bNrInPins, GFP_KERNEL); if (! namelist) { snd_printk(KERN_ERR "cannot malloc\n"); @@ -1769,7 +1788,9 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid, void if (! len) strlcpy(kctl->id.name, "USB", sizeof(kctl->id.name));
- if ((state->oterm.type & 0xff00) == 0x0100) + if (desc->bDescriptorSubtype == UAC2_CLOCK_SELECTOR) + append_ctl_name(kctl, " Clock Source"); + else if ((state->oterm.type & 0xff00) == 0x0100) append_ctl_name(kctl, " Capture Source"); else append_ctl_name(kctl, " Playback Source"); @@ -1803,10 +1824,12 @@ static int parse_audio_unit(struct mixer_build *state, int unitid)
switch (p1[2]) { case UAC_INPUT_TERMINAL: + case UAC2_CLOCK_SOURCE: return 0; /* NOP */ case UAC_MIXER_UNIT: return parse_audio_mixer_unit(state, unitid, p1); case UAC_SELECTOR_UNIT: + case UAC2_CLOCK_SELECTOR: return parse_audio_selector_unit(state, unitid, p1); case UAC_FEATURE_UNIT: return parse_audio_feature_unit(state, unitid, p1); @@ -1903,6 +1926,11 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer) err = parse_audio_unit(&state, desc->bSourceID); if (err < 0) return err; + + /* for UAC2, use the same approach to also add the clock selectors */ + err = parse_audio_unit(&state, desc->bCSourceID); + if (err < 0) + return err; } }
At Mon, 31 May 2010 13:35:36 +0200, Daniel Mack wrote:
Introduce two new static inline functions for a more readable parsing of UAC2 bmaControls.
Signed-off-by: Daniel Mack daniel@caiaq.de
Thanks! Now I applied all patches for 2.6.35-rc2.
But, basically I don't want to touch too much after this. So, please avoid refactoring patches for 2.6.35. If any, prepare a separate one for 2.6.36, and let's concentrate on fixes during 2.6.35-rc.
Takashi
include/linux/usb/audio-v2.h | 10 ++++++++++ sound/usb/mixer.c | 8 ++++---- 2 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/include/linux/usb/audio-v2.h b/include/linux/usb/audio-v2.h index 92f1d99..8f91be3 100644 --- a/include/linux/usb/audio-v2.h +++ b/include/linux/usb/audio-v2.h @@ -18,6 +18,16 @@ /* v1.0 and v2.0 of this standard have many things in common. For the rest
- of the definitions, please refer to audio.h */
+static inline bool uac2_control_is_readable(u32 bmControls, u8 control) +{
- return (bmControls >> (control * 2)) & 0x1;
+}
+static inline bool uac2_control_is_writeable(u32 bmControls, u8 control) +{
- return (bmControls >> (control * 2)) & 0x2;
+}
/* 4.7.2.1 Clock Source Descriptor */
struct uac_clock_source_descriptor { diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 03ce971..43d6417 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1188,9 +1188,9 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void
for (j = 0; j < channels; j++) { unsigned int mask = snd_usb_combine_bytes(bmaControls + csize * (j+1), csize);
if (mask & (1 << (i * 2))) {
if (uac2_control_is_readable(mask, i)) { ch_bits |= (1 << j);
if (~mask & (1 << ((i * 2) + 1)))
if (!uac2_control_is_writeable(mask, i)) ch_read_only |= (1 << j); } }
@@ -1198,9 +1198,9 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, void /* FIXME: the whole unit is read-only if any of the channels is marked read-only */ if (ch_bits & 1) /* the first channel must be set (for ease of programming) */ build_feature_ctl(state, _ftr, ch_bits, i, &iterm, unitid, !!ch_read_only);
if (master_bits & (1 << i * 2))
if (uac2_control_is_readable(master_bits, i)) build_feature_ctl(state, _ftr, 0, i, &iterm, unitid,
~master_bits & (1 << ((i * 2) + 1)));
} }!uac2_control_is_writeable(master_bits, i));
-- 1.7.1
On Mon, May 31, 2010 at 06:35:22PM +0200, Takashi Iwai wrote:
At Mon, 31 May 2010 13:35:36 +0200, Daniel Mack wrote:
Introduce two new static inline functions for a more readable parsing of UAC2 bmaControls.
Signed-off-by: Daniel Mack daniel@caiaq.de
Thanks! Now I applied all patches for 2.6.35-rc2.
But, basically I don't want to touch too much after this. So, please avoid refactoring patches for 2.6.35. If any, prepare a separate one for 2.6.36, and let's concentrate on fixes during 2.6.35-rc.
Yes, I'm aware of this, and I'm not planning to move this code around much in the next weeks, except for bug fixing.
In general, UAC2 devices are now almost fully supported. There are some special controllers missing like graphical equalizers, but that can be done when devices appear which really implement such things.
Happy testing :)
Thanks, Daniel
At Mon, 31 May 2010 18:47:16 +0200, Daniel Mack wrote:
On Mon, May 31, 2010 at 06:35:22PM +0200, Takashi Iwai wrote:
At Mon, 31 May 2010 13:35:36 +0200, Daniel Mack wrote:
Introduce two new static inline functions for a more readable parsing of UAC2 bmaControls.
Signed-off-by: Daniel Mack daniel@caiaq.de
Thanks! Now I applied all patches for 2.6.35-rc2.
But, basically I don't want to touch too much after this. So, please avoid refactoring patches for 2.6.35. If any, prepare a separate one for 2.6.36, and let's concentrate on fixes during 2.6.35-rc.
Yes, I'm aware of this, and I'm not planning to move this code around much in the next weeks, except for bug fixing.
That's helpful.
In general, UAC2 devices are now almost fully supported. There are some special controllers missing like graphical equalizers, but that can be done when devices appear which really implement such things.
Happy testing :)
Great, thanks for the info update.
Takashi
participants (2)
-
Daniel Mack
-
Takashi Iwai