ALSA: usb-audio: Add Audio Advantage Micro II
authorPrzemek Rudy <prudy1@o2.pl>
Thu, 27 Jun 2013 21:52:33 +0000 (23:52 +0200)
committerTakashi Iwai <tiwai@suse.de>
Fri, 28 Jun 2013 11:37:12 +0000 (13:37 +0200)
This patch is adding extensive support (beside standard usb audio class)
for Audio Advantage Micro II usb sound card.
Features included:
- Access to AES bits (so now sending the IEC61937 compliant stream is
possible).
- Mixer SPDIF control added to turn on/off the optical transmitter.

Signed-off-by: Przemek Rudy <prudy1@o2.pl>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/usb/mixer_quirks.c
sound/usb/quirks-table.h

index ebe91440a068a8f500901652892d5a3575b68e63..d42a584cf8292d99d349a309f55e2d1724e45898 100644 (file)
@@ -9,6 +9,8 @@
  *         Alan Cox (alan@lxorguk.ukuu.org.uk)
  *         Thomas Sailer (sailer@ife.ee.ethz.ch)
  *
+ *   Audio Advantage Micro II support added by:
+ *         Przemek Rudy (prudy1@o2.pl)
  *
  *   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
@@ -30,6 +32,7 @@
 #include <linux/usb.h>
 #include <linux/usb/audio.h>
 
+#include <sound/asoundef.h>
 #include <sound/core.h>
 #include <sound/control.h>
 #include <sound/hwdep.h>
@@ -1315,6 +1318,211 @@ static struct std_mono_table ebox44_table[] = {
        {}
 };
 
+/* Audio Advantage Micro II findings:
+ *
+ * Mapping spdif AES bits to vendor register.bit:
+ * AES0: [0 0 0 0 2.3 2.2 2.1 2.0] - default 0x00
+ * AES1: [3.3 3.2.3.1.3.0 2.7 2.6 2.5 2.4] - default: 0x01
+ * AES2: [0 0 0 0 0 0 0 0]
+ * AES3: [0 0 0 0 0 0 x 0] - 'x' bit is set basing on standard usb request
+ *                           (UAC_EP_CS_ATTR_SAMPLE_RATE) for Audio Devices
+ *
+ * power on values:
+ * r2: 0x10
+ * r3: 0x20 (b7 is zeroed just before playback (except IEC61937) and set
+ *           just after it to 0xa0, presumably it disables/mutes some analog
+ *           parts when there is no audio.)
+ * r9: 0x28
+ *
+ * Optical transmitter on/off:
+ * vendor register.bit: 9.1
+ * 0 - on (0x28 register value)
+ * 1 - off (0x2a register value)
+ *
+ */
+static int snd_microii_spdif_info(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_info *uinfo)
+{
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+       uinfo->count = 1;
+       return 0;
+}
+
+static int snd_microii_spdif_default_get(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol);
+       int err;
+       struct usb_interface *iface;
+       struct usb_host_interface *alts;
+       unsigned int ep;
+       unsigned char data[3];
+       int rate;
+
+       ucontrol->value.iec958.status[0] = kcontrol->private_value & 0xff;
+       ucontrol->value.iec958.status[1] = (kcontrol->private_value >> 8) & 0xff;
+       ucontrol->value.iec958.status[2] = 0x00;
+
+       /* use known values for that card: interface#1 altsetting#1 */
+       iface = usb_ifnum_to_if(mixer->chip->dev, 1);
+       alts = &iface->altsetting[1];
+       ep = get_endpoint(alts, 0)->bEndpointAddress;
+
+       err = snd_usb_ctl_msg(mixer->chip->dev,
+                       usb_rcvctrlpipe(mixer->chip->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));
+       if (err < 0)
+               goto end;
+
+       rate = data[0] | (data[1] << 8) | (data[2] << 16);
+       ucontrol->value.iec958.status[3] = (rate == 48000) ?
+                       IEC958_AES3_CON_FS_48000 : IEC958_AES3_CON_FS_44100;
+
+       err = 0;
+end:
+       return err;
+}
+
+static int snd_microii_spdif_default_put(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol);
+       int err;
+       u8 reg;
+       unsigned long priv_backup = kcontrol->private_value;
+
+       reg = ((ucontrol->value.iec958.status[1] & 0x0f) << 4) |
+                       (ucontrol->value.iec958.status[0] & 0x0f);
+       err = snd_usb_ctl_msg(mixer->chip->dev,
+                       usb_sndctrlpipe(mixer->chip->dev, 0),
+                       UAC_SET_CUR,
+                       USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+                       reg,
+                       2,
+                       NULL,
+                       0);
+       if (err < 0)
+               goto end;
+
+       kcontrol->private_value &= 0xfffff0f0;
+       kcontrol->private_value |= (ucontrol->value.iec958.status[1] & 0x0f) << 8;
+       kcontrol->private_value |= (ucontrol->value.iec958.status[0] & 0x0f);
+
+       reg = (ucontrol->value.iec958.status[0] & IEC958_AES0_NONAUDIO) ?
+                       0xa0 : 0x20;
+       reg |= (ucontrol->value.iec958.status[1] >> 4) & 0x0f;
+       err = snd_usb_ctl_msg(mixer->chip->dev,
+                       usb_sndctrlpipe(mixer->chip->dev, 0),
+                       UAC_SET_CUR,
+                       USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+                       reg,
+                       3,
+                       NULL,
+                       0);
+       if (err < 0)
+               goto end;
+
+       kcontrol->private_value &= 0xffff0fff;
+       kcontrol->private_value |= (ucontrol->value.iec958.status[1] & 0xf0) << 8;
+
+       /* The frequency bits in AES3 cannot be set via register access. */
+
+       /* Silently ignore any bits from the request that cannot be set. */
+
+       err = (priv_backup != kcontrol->private_value);
+end:
+       return err;
+}
+
+static int snd_microii_spdif_mask_get(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       ucontrol->value.iec958.status[0] = 0x0f;
+       ucontrol->value.iec958.status[1] = 0xff;
+       ucontrol->value.iec958.status[2] = 0x00;
+       ucontrol->value.iec958.status[3] = 0x00;
+
+       return 0;
+}
+
+static int snd_microii_spdif_switch_get(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       ucontrol->value.integer.value[0] = !(kcontrol->private_value & 0x02);
+
+       return 0;
+}
+
+static int snd_microii_spdif_switch_put(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol);
+       int err;
+       u8 reg = ucontrol->value.integer.value[0] ? 0x28 : 0x2a;
+
+       err = snd_usb_ctl_msg(mixer->chip->dev,
+                       usb_sndctrlpipe(mixer->chip->dev, 0),
+                       UAC_SET_CUR,
+                       USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+                       reg,
+                       9,
+                       NULL,
+                       0);
+
+       if (!err) {
+               err = (reg != (kcontrol->private_value & 0x0ff));
+               if (err)
+                       kcontrol->private_value = reg;
+       }
+
+       return err;
+}
+
+static struct snd_kcontrol_new snd_microii_mixer_spdif[] = {
+       {
+               .iface =    SNDRV_CTL_ELEM_IFACE_PCM,
+               .name =     SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+               .info =     snd_microii_spdif_info,
+               .get =      snd_microii_spdif_default_get,
+               .put =      snd_microii_spdif_default_put,
+               .private_value = 0x00000100UL,/* reset value */
+       },
+       {
+               .access =   SNDRV_CTL_ELEM_ACCESS_READ,
+               .iface =    SNDRV_CTL_ELEM_IFACE_PCM,
+               .name =     SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK),
+               .info =     snd_microii_spdif_info,
+               .get =      snd_microii_spdif_mask_get,
+       },
+       {
+               .iface =    SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name =     SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH),
+               .info =     snd_ctl_boolean_mono_info,
+               .get =      snd_microii_spdif_switch_get,
+               .put =      snd_microii_spdif_switch_put,
+               .private_value = 0x00000028UL,/* reset value */
+       }
+};
+
+static int snd_microii_controls_create(struct usb_mixer_interface *mixer)
+{
+       int err, i;
+
+       for (i = 0; i < ARRAY_SIZE(snd_microii_mixer_spdif); ++i) {
+               err = snd_ctl_add(mixer->chip->card,
+                       snd_ctl_new1(&snd_microii_mixer_spdif[i], mixer));
+               if (err < 0)
+                       return err;
+       }
+
+       return err;
+}
+
 int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
 {
        int err = 0;
@@ -1353,6 +1561,10 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
                err = snd_xonar_u1_controls_create(mixer);
                break;
 
+       case USB_ID(0x0d8c, 0x0103): /* Audio Advantage Micro II */
+               err = snd_microii_controls_create(mixer);
+               break;
+
        case USB_ID(0x17cc, 0x1011): /* Traktor Audio 6 */
                err = snd_nativeinstruments_create_mixer(mixer,
                                snd_nativeinstruments_ta6_mixers,
index 9c636c2d99f69b06c0968a10be58fa2c0dfa6ef7..f5f0595ef9c7a36bfef2d5c48c425a61e9dd796d 100644 (file)
@@ -3119,4 +3119,16 @@ YAMAHA_DEVICE(0x7010, "UB99"),
        }
 },
 
+{
+       /*
+        * The original product_name is "USB Sound Device", however this name
+        * is also used by the CM106 based cards, so make it unique.
+        */
+       USB_DEVICE(0x0d8c, 0x0103),
+       .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+               .product_name = "Audio Advantage MicroII",
+               .ifnum = QUIRK_NO_INTERFACE
+       }
+},
+
 #undef USB_DEVICE_VENDOR_SPEC