]> rtime.felk.cvut.cz Git - can-eth-gw-linux.git/blobdiff - sound/usb/stream.c
Merge tag 'sound-3.8' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound
[can-eth-gw-linux.git] / sound / usb / stream.c
index 1de0c8c002a8a2d35bc80e7de9a9c764a5945fc2..ad181d538bd9658edd79fec07d2fae125c9ebd38 100644 (file)
@@ -23,6 +23,8 @@
 
 #include <sound/core.h>
 #include <sound/pcm.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
 
 #include "usbaudio.h"
 #include "card.h"
@@ -47,6 +49,7 @@ static void free_substream(struct snd_usb_substream *subs)
        list_for_each_safe(p, n, &subs->fmt_list) {
                struct audioformat *fp = list_entry(p, struct audioformat, list);
                kfree(fp->rate_table);
+               kfree(fp->chmap);
                kfree(fp);
        }
        kfree(subs->rate_list.list);
@@ -99,6 +102,206 @@ static void snd_usb_init_substream(struct snd_usb_stream *as,
        subs->num_formats++;
        subs->fmt_type = fp->fmt_type;
        subs->ep_num = fp->endpoint;
+       if (fp->channels > subs->channels_max)
+               subs->channels_max = fp->channels;
+}
+
+/* kctl callbacks for usb-audio channel maps */
+static int usb_chmap_ctl_info(struct snd_kcontrol *kcontrol,
+                             struct snd_ctl_elem_info *uinfo)
+{
+       struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
+       struct snd_usb_substream *subs = info->private_data;
+
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = subs->channels_max;
+       uinfo->value.integer.min = 0;
+       uinfo->value.integer.max = SNDRV_CHMAP_LAST;
+       return 0;
+}
+
+/* check whether a duplicated entry exists in the audiofmt list */
+static bool have_dup_chmap(struct snd_usb_substream *subs,
+                          struct audioformat *fp)
+{
+       struct list_head *p;
+
+       for (p = fp->list.prev; p != &subs->fmt_list; p = p->prev) {
+               struct audioformat *prev;
+               prev = list_entry(p, struct audioformat, list);
+               if (prev->chmap &&
+                   !memcmp(prev->chmap, fp->chmap, sizeof(*fp->chmap)))
+                       return true;
+       }
+       return false;
+}
+
+static int usb_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
+                            unsigned int size, unsigned int __user *tlv)
+{
+       struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
+       struct snd_usb_substream *subs = info->private_data;
+       struct audioformat *fp;
+       unsigned int __user *dst;
+       int count = 0;
+
+       if (size < 8)
+               return -ENOMEM;
+       if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv))
+               return -EFAULT;
+       size -= 8;
+       dst = tlv + 2;
+       list_for_each_entry(fp, &subs->fmt_list, list) {
+               int i, ch_bytes;
+
+               if (!fp->chmap)
+                       continue;
+               if (have_dup_chmap(subs, fp))
+                       continue;
+               /* copy the entry */
+               ch_bytes = fp->chmap->channels * 4;
+               if (size < 8 + ch_bytes)
+                       return -ENOMEM;
+               if (put_user(SNDRV_CTL_TLVT_CHMAP_FIXED, dst) ||
+                   put_user(ch_bytes, dst + 1))
+                       return -EFAULT;
+               dst += 2;
+               for (i = 0; i < fp->chmap->channels; i++, dst++) {
+                       if (put_user(fp->chmap->map[i], dst))
+                               return -EFAULT;
+               }
+
+               count += 8 + ch_bytes;
+               size -= 8 + ch_bytes;
+       }
+       if (put_user(count, tlv + 1))
+               return -EFAULT;
+       return 0;
+}
+
+static int usb_chmap_ctl_get(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
+       struct snd_usb_substream *subs = info->private_data;
+       struct snd_pcm_chmap_elem *chmap = NULL;
+       int i;
+
+       memset(ucontrol->value.integer.value, 0,
+              sizeof(ucontrol->value.integer.value));
+       if (subs->cur_audiofmt)
+               chmap = subs->cur_audiofmt->chmap;
+       if (chmap) {
+               for (i = 0; i < chmap->channels; i++)
+                       ucontrol->value.integer.value[i] = chmap->map[i];
+       }
+       return 0;
+}
+
+/* create a chmap kctl assigned to the given USB substream */
+static int add_chmap(struct snd_pcm *pcm, int stream,
+                    struct snd_usb_substream *subs)
+{
+       struct audioformat *fp;
+       struct snd_pcm_chmap *chmap;
+       struct snd_kcontrol *kctl;
+       int err;
+
+       list_for_each_entry(fp, &subs->fmt_list, list)
+               if (fp->chmap)
+                       goto ok;
+       /* no chmap is found */
+       return 0;
+
+ ok:
+       err = snd_pcm_add_chmap_ctls(pcm, stream, NULL, 0, 0, &chmap);
+       if (err < 0)
+               return err;
+
+       /* override handlers */
+       chmap->private_data = subs;
+       kctl = chmap->kctl;
+       kctl->info = usb_chmap_ctl_info;
+       kctl->get = usb_chmap_ctl_get;
+       kctl->tlv.c = usb_chmap_ctl_tlv;
+
+       return 0;
+}
+
+/* convert from USB ChannelConfig bits to ALSA chmap element */
+static struct snd_pcm_chmap_elem *convert_chmap(int channels, unsigned int bits,
+                                               int protocol)
+{
+       static unsigned int uac1_maps[] = {
+               SNDRV_CHMAP_FL,         /* left front */
+               SNDRV_CHMAP_FR,         /* right front */
+               SNDRV_CHMAP_FC,         /* center front */
+               SNDRV_CHMAP_LFE,        /* LFE */
+               SNDRV_CHMAP_SL,         /* left surround */
+               SNDRV_CHMAP_SR,         /* right surround */
+               SNDRV_CHMAP_FLC,        /* left of center */
+               SNDRV_CHMAP_FRC,        /* right of center */
+               SNDRV_CHMAP_RC,         /* surround */
+               SNDRV_CHMAP_SL,         /* side left */
+               SNDRV_CHMAP_SR,         /* side right */
+               SNDRV_CHMAP_TC,         /* top */
+               0 /* terminator */
+       };
+       static unsigned int uac2_maps[] = {
+               SNDRV_CHMAP_FL,         /* front left */
+               SNDRV_CHMAP_FR,         /* front right */
+               SNDRV_CHMAP_FC,         /* front center */
+               SNDRV_CHMAP_LFE,        /* LFE */
+               SNDRV_CHMAP_RL,         /* back left */
+               SNDRV_CHMAP_RR,         /* back right */
+               SNDRV_CHMAP_FLC,        /* front left of center */
+               SNDRV_CHMAP_FRC,        /* front right of center */
+               SNDRV_CHMAP_RC,         /* back center */
+               SNDRV_CHMAP_SL,         /* side left */
+               SNDRV_CHMAP_SR,         /* side right */
+               SNDRV_CHMAP_TC,         /* top center */
+               SNDRV_CHMAP_TFL,        /* top front left */
+               SNDRV_CHMAP_TFC,        /* top front center */
+               SNDRV_CHMAP_TFR,        /* top front right */
+               SNDRV_CHMAP_TRL,        /* top back left */
+               SNDRV_CHMAP_TRC,        /* top back center */
+               SNDRV_CHMAP_TRR,        /* top back right */
+               SNDRV_CHMAP_TFLC,       /* top front left of center */
+               SNDRV_CHMAP_TFRC,       /* top front right of center */
+               SNDRV_CHMAP_LLFE,       /* left LFE */
+               SNDRV_CHMAP_RLFE,       /* right LFE */
+               SNDRV_CHMAP_TSL,        /* top side left */
+               SNDRV_CHMAP_TSR,        /* top side right */
+               SNDRV_CHMAP_BC,         /* bottom center */
+               SNDRV_CHMAP_BLC,        /* bottom left center */
+               SNDRV_CHMAP_BRC,        /* bottom right center */
+               0 /* terminator */
+       };
+       struct snd_pcm_chmap_elem *chmap;
+       const unsigned int *maps;
+       int c;
+
+       if (!bits)
+               return NULL;
+       if (channels > ARRAY_SIZE(chmap->map))
+               return NULL;
+
+       chmap = kzalloc(sizeof(*chmap), GFP_KERNEL);
+       if (!chmap)
+               return NULL;
+
+       maps = protocol == UAC_VERSION_2 ? uac2_maps : uac1_maps;
+       chmap->channels = channels;
+       c = 0;
+       for (; bits && *maps; maps++, bits >>= 1) {
+               if (bits & 1)
+                       chmap->map[c++] = *maps;
+       }
+
+       for (; c < channels; c++)
+               chmap->map[c] = SNDRV_CHMAP_UNKNOWN;
+
+       return chmap;
 }
 
 /*
@@ -140,7 +343,7 @@ int snd_usb_add_audio_stream(struct snd_usb_audio *chip,
                if (err < 0)
                        return err;
                snd_usb_init_substream(as, stream, fp);
-               return 0;
+               return add_chmap(as->pcm, stream, subs);
        }
 
        /* create a new pcm */
@@ -174,7 +377,7 @@ int snd_usb_add_audio_stream(struct snd_usb_audio *chip,
 
        snd_usb_proc_pcm_format_add(as);
 
-       return 0;
+       return add_chmap(pcm, stream, &as->substream[stream]);
 }
 
 static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip,
@@ -218,8 +421,11 @@ 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,
+/* find an input terminal descriptor (either UAC1 or UAC2) with the given
+ * terminal id
+ */
+static void *
+snd_usb_find_input_terminal_descriptor(struct usb_host_interface *ctrl_iface,
                                               int terminal_id)
 {
        struct uac2_input_terminal_descriptor *term = NULL;
@@ -261,6 +467,7 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no)
        struct audioformat *fp = NULL;
        int num, protocol, clock = 0;
        struct uac_format_type_i_continuous_descriptor *fmt;
+       unsigned int chconfig;
 
        dev = chip->dev;
 
@@ -300,6 +507,7 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no)
                if (snd_usb_apply_interface_quirk(chip, iface_no, altno))
                        continue;
 
+               chconfig = 0;
                /* get audio formats */
                switch (protocol) {
                default:
@@ -311,6 +519,7 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no)
                case UAC_VERSION_1: {
                        struct uac1_as_header_descriptor *as =
                                snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, UAC_AS_GENERAL);
+                       struct uac_input_terminal_descriptor *iterm;
 
                        if (!as) {
                                snd_printk(KERN_ERR "%d:%u:%d : UAC_AS_GENERAL descriptor not found\n",
@@ -325,6 +534,14 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no)
                        }
 
                        format = le16_to_cpu(as->wFormatTag); /* remember the format value */
+
+                       iterm = snd_usb_find_input_terminal_descriptor(chip->ctrl_intf,
+                                                                      as->bTerminalLink);
+                       if (iterm) {
+                               num_channels = iterm->bNrChannels;
+                               chconfig = le16_to_cpu(iterm->wChannelConfig);
+                       }
+
                        break;
                }
 
@@ -355,6 +572,7 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no)
                                                                            as->bTerminalLink);
                        if (input_term) {
                                clock = input_term->bCSourceID;
+                               chconfig = le32_to_cpu(input_term->bmChannelConfig);
                                break;
                        }
 
@@ -413,13 +631,13 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no)
                fp->ep_attr = get_endpoint(alts, 0)->bmAttributes;
                fp->datainterval = snd_usb_parse_datainterval(chip, alts);
                fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
-               /* num_channels is only set for v2 interfaces */
                fp->channels = num_channels;
                if (snd_usb_get_speed(dev) == USB_SPEED_HIGH)
                        fp->maxpacksize = (((fp->maxpacksize >> 11) & 3) + 1)
                                        * (fp->maxpacksize & 0x7ff);
                fp->attributes = parse_uac_endpoint_attributes(chip, alts, protocol, iface_no);
                fp->clock = clock;
+               fp->chmap = convert_chmap(num_channels, chconfig, protocol);
 
                /* some quirks for attributes here */
 
@@ -455,6 +673,7 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no)
                /* ok, let's parse further... */
                if (snd_usb_parse_audio_format(chip, fp, format, fmt, stream, alts) < 0) {
                        kfree(fp->rate_table);
+                       kfree(fp->chmap);
                        kfree(fp);
                        fp = NULL;
                        continue;
@@ -464,6 +683,7 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip, int iface_no)
                err = snd_usb_add_audio_stream(chip, stream, fp);
                if (err < 0) {
                        kfree(fp->rate_table);
+                       kfree(fp->chmap);
                        kfree(fp);
                        return err;
                }