ALSA: usb-audio: add support for UAC2 pitch control
[safe/jmp/linux-2.6] / sound / usb / pcm.c
index c3d5a97..056587d 100644 (file)
  */
 
 #include <linux/init.h>
+#include <linux/slab.h>
 #include <linux/usb.h>
 #include <linux/usb/audio.h>
+#include <linux/usb/audio-v2.h>
 
 #include <sound/core.h>
 #include <sound/pcm.h>
@@ -58,7 +60,9 @@ static struct audioformat *find_format(struct snd_usb_substream *subs, unsigned
        list_for_each(p, &subs->fmt_list) {
                struct audioformat *fp;
                fp = list_entry(p, struct audioformat, list);
-               if (fp->format != format || fp->channels != channels)
+               if (!(fp->formats & (1uLL << format)))
+                       continue;
+               if (fp->channels != channels)
                        continue;
                if (rate < fp->rate_min || rate > fp->rate_max)
                        continue;
@@ -105,69 +109,173 @@ static struct audioformat *find_format(struct snd_usb_substream *subs, unsigned
        return found;
 }
 
+static int init_pitch_v1(struct snd_usb_audio *chip, int iface,
+                        struct usb_host_interface *alts,
+                        struct audioformat *fmt)
+{
+       struct usb_device *dev = chip->dev;
+       unsigned int ep;
+       unsigned char data[1];
+       int err;
+
+       ep = get_endpoint(alts, 0)->bEndpointAddress;
+
+       data[0] = 1;
+       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_PITCH_CONTROL << 8, ep,
+                                  data, sizeof(data), 1000)) < 0) {
+               snd_printk(KERN_ERR "%d:%d:%d: cannot set enable PITCH\n",
+                          dev->devnum, iface, ep);
+               return err;
+       }
+
+       return 0;
+}
+
+static int init_pitch_v2(struct snd_usb_audio *chip, int iface,
+                        struct usb_host_interface *alts,
+                        struct audioformat *fmt)
+{
+       struct usb_device *dev = chip->dev;
+       unsigned char data[1];
+       unsigned int ep;
+       int err;
+
+       ep = get_endpoint(alts, 0)->bEndpointAddress;
+
+       data[0] = 1;
+       if ((err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), UAC2_CS_CUR,
+                                  USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_OUT,
+                                  UAC2_EP_CS_PITCH << 8, 0,
+                                  data, sizeof(data), 1000)) < 0) {
+               snd_printk(KERN_ERR "%d:%d:%d: cannot set enable PITCH (v2)\n",
+                          dev->devnum, iface, fmt->altsetting);
+               return err;
+       }
+
+       return 0;
+}
 
 /*
- * initialize the picth control and sample rate
+ * initialize the pitch control and sample rate
  */
-int snd_usb_init_pitch(struct usb_device *dev, int iface,
+int snd_usb_init_pitch(struct snd_usb_audio *chip, int iface,
                       struct usb_host_interface *alts,
                       struct audioformat *fmt)
 {
+       struct usb_interface_descriptor *altsd = get_iface_desc(alts);
+
+       /* if endpoint doesn't have pitch control, bail out */
+       if (!(fmt->attributes & UAC_EP_CS_ATTR_PITCH_CONTROL))
+               return 0;
+
+       switch (altsd->bInterfaceProtocol) {
+       case UAC_VERSION_1:
+               return init_pitch_v1(chip, iface, alts, fmt);
+
+       case UAC_VERSION_2:
+               return init_pitch_v2(chip, iface, alts, fmt);
+       }
+
+       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[1];
-       int err;
+       unsigned char data[3];
+       int err, crate;
 
        ep = get_endpoint(alts, 0)->bEndpointAddress;
-       /* if endpoint has pitch control, enable it */
-       if (fmt->attributes & UAC_EP_CS_ATTR_PITCH_CONTROL) {
-               data[0] = 1;
-               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_PITCH_CONTROL << 8, ep, data, 1, 1000)) < 0) {
-                       snd_printk(KERN_ERR "%d:%d:%d: cannot set enable PITCH\n",
-                                  dev->devnum, iface, ep);
-                       return err;
-               }
+       /* 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;
 }
 
-int snd_usb_init_sample_rate(struct usb_device *dev, int iface,
+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)
 {
-       unsigned int ep;
-       unsigned char data[3];
-       int err;
+       struct usb_interface_descriptor *altsd = get_iface_desc(alts);
 
-       ep = get_endpoint(alts, 0)->bEndpointAddress;
-       /* if endpoint has sampling rate control, set it */
-       if (fmt->attributes & UAC_EP_CS_ATTR_SAMPLE_RATE) {
-               int crate;
-               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, 3, 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, 3, 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;
-               }
+       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 0;
+
+       return -EINVAL;
 }
 
 /*
@@ -278,7 +386,7 @@ static int set_format(struct snd_usb_substream *subs, struct audioformat *fmt)
        if (fmt->attributes & UAC_EP_CS_ATTR_FILL_MAX)
                subs->fill_max = 1;
 
-       if ((err = snd_usb_init_pitch(dev, subs->interface, alts, fmt)) < 0)
+       if ((err = snd_usb_init_pitch(subs->stream->chip, subs->interface, alts, fmt)) < 0)
                return err;
 
        subs->cur_audiofmt = fmt;
@@ -341,7 +449,7 @@ static int snd_usb_hw_params(struct snd_pcm_substream *substream,
                struct usb_interface *iface;
                iface = usb_ifnum_to_if(subs->dev, fmt->iface);
                alts = &iface->altsetting[fmt->altset_idx];
-               ret = snd_usb_init_sample_rate(subs->dev, subs->interface, alts, fmt, rate);
+               ret = snd_usb_init_sample_rate(subs->stream->chip, subs->interface, alts, fmt, rate);
                if (ret < 0)
                        return ret;
                subs->cur_rate = rate;
@@ -428,10 +536,15 @@ static int hw_check_valid_format(struct snd_usb_substream *subs,
        struct snd_interval *ct = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
        struct snd_mask *fmts = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
        struct snd_interval *pt = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_TIME);
+       struct snd_mask check_fmts;
        unsigned int ptime;
 
        /* check the format */
-       if (!snd_mask_test(fmts, fp->format)) {
+       snd_mask_none(&check_fmts);
+       check_fmts.bits[0] = (u32)fp->formats;
+       check_fmts.bits[1] = (u32)(fp->formats >> 32);
+       snd_mask_intersect(&check_fmts, fmts);
+       if (snd_mask_empty(&check_fmts)) {
                hwc_debug("   > check: no supported format %d\n", fp->format);
                return 0;
        }
@@ -584,7 +697,7 @@ static int hw_rule_format(struct snd_pcm_hw_params *params,
                fp = list_entry(p, struct audioformat, list);
                if (!hw_check_valid_format(subs, params, fp))
                        continue;
-               fbits |= (1ULL << fp->format);
+               fbits |= fp->formats;
        }
 
        oldbits[0] = fmt->bits[0];