diff options
author | Igor Pashev <pashev.igor@gmail.com> | 2013-05-03 21:08:42 +0400 |
---|---|---|
committer | Igor Pashev <pashev.igor@gmail.com> | 2013-05-03 21:08:42 +0400 |
commit | 1058def8e7827e56ce4a70afb4aeacb5dc44148f (patch) | |
tree | 4495d23e7b54ab5700e3839081e797c1eafe0db9 /kernel/drv/oss_usb/oss_usb.c | |
download | oss4-upstream/4.2-build2006.tar.gz |
Imported Upstream version 4.2-build2006upstream/4.2-build2006upstream
Diffstat (limited to 'kernel/drv/oss_usb/oss_usb.c')
-rw-r--r-- | kernel/drv/oss_usb/oss_usb.c | 2505 |
1 files changed, 2505 insertions, 0 deletions
diff --git a/kernel/drv/oss_usb/oss_usb.c b/kernel/drv/oss_usb/oss_usb.c new file mode 100644 index 0000000..2b5f119 --- /dev/null +++ b/kernel/drv/oss_usb/oss_usb.c @@ -0,0 +1,2505 @@ +/* + * Purpose: Top level USB audio class initialization and mixer functions + */ + +/* + * + * This file is part of Open Sound System. + * + * Copyright (C) 4Front Technologies 1996-2008. + * + * This this source file is released under GPL v2 license (no other versions). + * See the COPYING file included in the main directory of this source + * distribution for the license terms and conditions. + * + */ + +#define KBUILD_MODNAME oss_usb +#include "oss_config.h" +#include "ossusb.h" + + +static const udi_usb_devinfo known_devices[] = { + {0x763, 0x2002, "M Audio USB AudioSport Duo", + { + {"OFF 24bit_hispeed 16bit_hispeed 24bit_lowspeed 16bit_lowspeed", 2, + 0x0014}, + {"OFF 24bit_hispeed 16bit_hispeed 24bit_lowspeed 16bit_lowspeed", 2, + 0x0014} + } + }, + {0x763, 0x2001, "M Audio USB AudioSport Quatro"}, + {0x763, 0x2805, "M Audio Sonica"}, + {0x763, 0x2007, "M Audio Sonica Theatre", + { + {"OFF 24bit_hispeed 16bit_hispeed 16bit_hispeed 24bit_lowspeed 24bit24bit 16bit 16bit OFF OFF", 3}, + {"OFF 24bit_hispeed 16bit_hispeed 24bit 16bit_lowspeed", 2} + } + }, + {0x763, 0x200d, "M Audio OmniStudio USB"}, + {0x41e, 0x3000, "Creative Sound Blaster Extigy"}, + {0x41e, 0x3010, "Creative Sound Blaster MP3+"}, + {0x41e, 0x3020, "Creative Audigy2 NX USB", + { + {"OFF 16bit22kHz 16bit44kHz " /* 0-2 */ + "24bit44kHz 16bit48kHz 24bit48kHz " /* 3-5 */ + "16bit96kHz 24bit96kHz 16bit22kHz " /* 6-8 */ + "16bit48kHz 24bit48kHz 16bit5.1ch22kHz " /* 9-11 */ + "16bit5.1ch48kHz 24bit5.1ch48kHz 16bit7.1ch22kHz " /* 12-14 */ + "16bit7.1ch48kHz" /* 15 */ , 4}, + + {"OFF 16bit32kHz 24bit32kHz " /* 0-2 */ + "16bit44kHz 24bit44kHz 16bit48kHz " /* 3-5 */ + "24bit48kHzin 24bit96kHz 24bit96kHz ", /* 6-8 */ + 5} + } + }, + {0x46d, 0x8b2, "Logitec Quickcam Pro 4000 (mic)"}, + {0x46d, 0xa01, "Logitec USB Headset"}, + {0x0471, 0x0311, "Philips ToUcam Pro (mic)"}, + {0x672, 0x1041, "Labtec LCS1040 Speaker System"}, + {0x6f8, 0xc000, "Hercules Gamesurround MUSE Pocket"}, + {0x0d8c, 0x000c, "C-Media USB audio"}, + {0x0d8c, 0x0102, "C-Media 2/4/6/8ch USB audio", + { + {"OFF 7.1 2.0 3.1 5.1 digital", 2}, + {"OFF stereo"} + } + }, + {0x0d8c, 0x0103, "C-Media USB audio"}, + {-1, -1, "USB sound device"} +}; + +typedef struct +{ + unsigned int devid; + special_driver_t driver; +} +special_table_t; + +extern int usb_mixerstyle; + +#define MAX_DEVC 10 +static ossusb_devc *devc_list[MAX_DEVC]; +static int ndevs = 0; +int usb_quiet = 0; + + +void +ossusb_dump_desc (unsigned char *desc, int cfg_len) +{ + int i; + + for (i = 0; i < cfg_len; i++) + { + if (!(i % 16)) + cmn_err (CE_CONT, "\n%04x: ", i); + cmn_err (CE_CONT, "%02x ", desc[i]); + } + cmn_err (CE_CONT, "\n"); +} + +unsigned int +ossusb_get_int (unsigned char *p, int nbytes) +{ + unsigned int v = 0; + int i; + + for (i = 0; i < nbytes; i++) + { + v |= (*p++) << (i * 8); + } + + return v; +} + +/* + * Mixer stuff + */ + +static int +decode_vol (usb_control_t * c, unsigned char *buf, int l) +{ + int value, range; + + if (l == 1) + value = (signed char) buf[0]; + else + value = (signed short) (buf[0] | (buf[1] << 8)); + + range = c->max - c->min; + + value -= c->min; + value = (value * c->scale + range / 2) / range; + + if (value < 0) + value = 0; + if (value > 255) + value = 255; + return value; +} + +static void +encode_vol (usb_control_t * c, unsigned char *buf, int l, int value) +{ + int range; + + range = c->max - c->min; + + value = (value * range + c->scale / 2) / c->scale; + + value += c->min; + + if (l == 1) + buf[0] = value; + else + { + buf[0] = value & 0xff; + buf[1] = (value >> 8) & 0xff; + } + +} + +static int +read_feature_value (ossusb_devc * devc, usb_control_t * c, int rq, int *v) +{ + int len, value = 0, l; + int nch, ch; + unsigned char buf[2]; + +/* + * Volume controls use two message bytes while the others use just one. + */ + l = (c->index == 1) ? 2 : 1; + + ch = c->min_ch + 1; + if (c->global) + ch = 0; + + if (rq != GET_CUR) + { + buf[0] = buf[1] = 0; + len = udi_usb_rcv_control_msg (devc->mixer_usbdev, 0, // endpoint + rq, USB_RECIP_INTERFACE | USB_TYPE_CLASS, // rqtype + ((c->index + 1) << 8) | (ch), // value + (c->unit->num << 8), // index + buf, // buffer + l, // buflen + OSS_HZ * 2); + if (len < 0) + return 0; + + value = + (len == + 1) ? (signed char) buf[0] : (signed short) (buf[0] | (buf[1] << 8)); + *v = value; + return 1; + } + + nch = c->max_ch - c->min_ch + 1; + + buf[0] = buf[1] = 0; + len = udi_usb_rcv_control_msg (devc->mixer_usbdev, 0, // endpoint + GET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS, // rqtype + ((c->index + 1) << 8) | (ch), // value + (c->unit->num << 8), // index + buf, // buffer + l, // buflen + OSS_HZ * 2); + if (len < 0) + { + // cmn_err(CE_CONT, "feature (%s/%d) read error %d\n", c->unit->name, c->index, len); + *v = 0; + return 0; + } + + value = decode_vol (c, buf, len); + + if (!c->global) + if (nch == 2) /* Read the right channel */ + { + len = udi_usb_rcv_control_msg (devc->mixer_usbdev, 0, // endpoint + GET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS, // rqtype + ((c->index + 1) << 8) | (ch + 1), // value + (c->unit->num << 8), // index + buf, // buffer + l, // buflen + OSS_HZ * 2); + if (len < 0) + value |= (value & 0xff) << 8; + else + value |= (decode_vol (c, buf, len)) << 8; + } + + *v = value; + return 1; +} + +static int +write_feature_value (ossusb_devc * devc, usb_control_t * c, int value) +{ + int len, l; + int left, right; + int ch, nch; + unsigned char buf[2]; + + ch = c->min_ch + 1; + if (c->global) + ch = 0; + + left = value & 0xff; + right = (value >> 8) & 0xff; + + if (left > (c->max - c->min)) + left = c->max - c->min; + if (right > (c->max - c->min)) + right = c->max - c->min; + + l = (c->index == 1) ? 2 : 1; + + nch = c->max_ch - c->min_ch + 1; + buf[0] = buf[1] = 0; + encode_vol (c, buf, l, left); + + len = udi_usb_snd_control_msg (devc->mixer_usbdev, 0, // endpoint + SET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS, // rqtype + ((c->index + 1) << 8) | ch, // value + (c->unit->num << 8), // index + buf, // buffer + l, // buflen + OSS_HZ * 2); + if (len < 0) + { + cmn_err (CE_CONT, "feature write error %d\n", len); + return len; + } + + if (nch == 2) /* Write the right channel */ + { + buf[0] = buf[1] = 0; + encode_vol (c, buf, l, right); + + len = udi_usb_snd_control_msg (devc->mixer_usbdev, 0, // endpoint + SET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS, // rqtype + ((c->index + 1) << 8) | (ch + 1), // value + (c->unit->num << 8), // index + buf, // buffer + l, // buflen + OSS_HZ * 2); + } + else + right = left; + + value = left | (right << 8); + return value; +} + +static int +read_mixer_value (ossusb_devc * devc, usb_control_t * c, int rq, int *v) +{ + int len, value = 0; + unsigned char buf[2]; + + if (rq != GET_CUR) + { + len = udi_usb_rcv_control_msg (devc->mixer_usbdev, 0, // endpoint + rq, USB_RECIP_INTERFACE | USB_TYPE_CLASS, // rqtype + (c->index << 8) | c->min_ch, // value + (c->unit->num << 8) | 1, // index + buf, // buffer + 2, // buflen + OSS_HZ * 2); + if (len < 0) + return 0; + + *v = (signed char) buf[1]; + return 1; + } + + len = udi_usb_rcv_control_msg (devc->mixer_usbdev, 0, // endpoint + rq, USB_RECIP_INTERFACE | USB_TYPE_CLASS, // rqtype + (c->index << 8) | c->min_ch, // value + (c->unit->num << 8) | 1, // index + buf, // buffer + 2, // buflen + OSS_HZ * 2); + if (len < 0) + { + cmn_err (CE_CONT, "mixer read error %d\n", len); + return 0; + } + + value = buf[1] - c->min; + + *v = value; + return 1; +} + +static int +write_mixer_value (ossusb_devc * devc, usb_control_t * c, int value) +{ + int len; + unsigned char buf[2]; + + value &= 0xff; + + buf[0] = 0; + buf[1] = value + c->min; + len = udi_usb_snd_control_msg (devc->mixer_usbdev, 0, // endpoint + SET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS, // rqtype + (c->index << 8) | c->min_ch, // value + (c->unit->num << 8) | 1, // index + buf, // buffer + 2, // buflen + OSS_HZ * 2); + if (len < 0) + { + cmn_err (CE_CONT, "mixer write error %d\n", len); + return 0; + } + + return value; +} + +static int +read_unit (ossusb_devc * devc, usb_control_t * c, int rq, int *v) +{ + int err, value; + unsigned char buf[2]; + + *v = value = 0; + + switch (c->unit->typ) + { + case TY_FEAT: + return read_feature_value (devc, c, rq, v); + break; + + case TY_MIXER: + return read_mixer_value (devc, c, rq, v); + break; + + case TY_SELECT: + err = udi_usb_rcv_control_msg (devc->mixer_usbdev, 0, // endpoint + rq, USB_RECIP_INTERFACE | USB_TYPE_CLASS, // rqtype + 0, // value + (c->unit->num << 8), // index + buf, // buffer + 1, // buflen + OSS_HZ * 2); + if (err < 0) + { + cmn_err (CE_CONT, "USB mixer unit read error %d\n", err); + return 0; + } + value = buf[0] - 1; + break; + + } /* switch */ + + *v = value; + return 1; +} + +static int +read_current_value (ossusb_devc * devc, usb_control_t * c) +{ + int v; + + if (!read_unit (devc, c, GET_CUR, &v)) + return 0; + + c->value = v; + + return 1; +} + +static int +write_current_value (ossusb_devc * devc, usb_control_t * c, int value) +{ + unsigned char buf[2]; + int err; + + switch (c->unit->typ) + { + case TY_SELECT: + if (value > c->max) + value = c->max; + buf[0] = value + 1; + err = udi_usb_snd_control_msg (devc->mixer_usbdev, 0, // endpoint + SET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS, // rqtype + 0, // value + (c->unit->num << 8), // index + buf, // buffer + 1, // buflen + OSS_HZ * 2); + if (err < 0) + { + cmn_err (CE_CONT, "Selector write error %d\n", err); + return 0; + } + break; + + case TY_FEAT: + value = write_feature_value (devc, c, value); + break; + + case TY_MIXER: + value = write_mixer_value (devc, c, value); + break; + + default: + return OSS_EIO; + } + + return value; +} + +static int +new_ctl (ossusb_devc * devc, usb_audio_unit_t * un, int index, int exttype, + int chmask) +{ + int n, min, max; + usb_control_t *c; + int i, min_ch = 0, max_ch = 0; + + if (devc->ncontrols >= MAX_CONTROLS) + { + cmn_err (CE_CONT, "Too many mixer features.\n"); + return OSS_EIO; + } + + n = devc->ncontrols++; + + c = &devc->controls[n]; + + if (chmask) + { + min_ch = -1; + + for (i = 0; i < 32; i++) + if (chmask & (1 << i)) + { + if (min_ch == -1) + min_ch = i; + max_ch = i; + } + } + + c->unit = un; + c->index = index; + c->exttype = exttype; + c->min_ch = min_ch; + c->global = (chmask == 0) ? 1 : 0; + c->max_ch = max_ch; + c->min = 0; + c->max = 255; + c->chmask = chmask; + c->value = 0; + + if (index != 1) /* Not volume control */ + { + c->max = 1; + } + else + { + if (read_unit (devc, c, GET_MAX, &max)) + { + c->max = max; + if (read_unit (devc, c, GET_MIN, &min)) + { + c->min = min; + } + } + } + + if (c->max <= c->min) + { +/* + * The device reported bad limits. Try to fix the situation. + */ + if (c->min < 0) + c->max = 0; + else + c->min = 0; + if (c->max <= c->min) + { + c->min = 0; + c->max = 255; + } + } + + c->scale = 255; + if (c->max - c->min < 255) + c->scale = c->max - c->min; + + + read_current_value (devc, c); + + if (usb_trace) + { + cmn_err (CE_CONT, "Ctl %2d: %d/%s %d ", n, c->unit->num, c->unit->name, + c->index); + cmn_err (CE_CONT, "Min %d, max %d, scale %d\n", c->min, c->max, + c->scale); + cmn_err (CE_CONT, "ch=%x ", c->chmask); + cmn_err (CE_CONT, "Value %04x, (%d - %d, %d)\n", c->value, c->min, + c->max, c->scale); + } + + return n; +} + +static int +mixer_func (int dev, int ctrl, unsigned int cmd, int value) +{ + ossusb_devc *devc = mixer_devs[dev]->devc; + usb_control_t *c; + + if (devc->disabled) + return OSS_EPIPE; + + if (ctrl < 0 || ctrl >= devc->ncontrols) + return OSS_EIO; + + c = &devc->controls[ctrl]; + + if (cmd == SNDCTL_MIX_READ) + { + return c->value; + } + + if (cmd != SNDCTL_MIX_WRITE) + return OSS_EINVAL; + + value = c->value = write_current_value (devc, c, value); + + return value; +} + +static char * +get_feature_name (int n) +{ + switch (n) + { + case 0: + return "mute"; + case 1: + return "vol"; + case 2: + return "bass"; + case 3: + return "mid"; + case 4: + return "treble"; + case 5: + return "eq"; + case 6: + return "agc"; + case 7: + return "delay"; + case 8: + return "boost"; + case 9: + return "loud"; + case 10: + return "igain"; + case 11: + return "igainpad"; + case 12: + return "phaseinv"; + case 13: + return "underflow"; + case 14: + return "overflow"; + default: + return "misc"; + } +} + +static int +get_feature_type (int n) +{ + switch (n) + { + case 0: + return MIXT_ONOFF; + case 1: + return MIXT_SLIDER; + case 2: + return MIXT_SLIDER; + case 3: + return MIXT_SLIDER; + case 4: + return MIXT_SLIDER; + case 5: + return MIXT_SLIDER; + case 6: + return MIXT_ONOFF; + case 7: + return MIXT_SLIDER; + case 8: + return MIXT_ONOFF; + case 9: + return MIXT_ONOFF; + default: + return MIXT_ONOFF; + } +} + +struct chmasks +{ + unsigned int mask; + char *name; + char type; + char channels; +}; + +static const struct chmasks chmasks[] = { + {0x00000003, "front", MIXT_STEREOSLIDER, 2}, + {0x00000001, "L", MIXT_MONOSLIDER, 1}, + {0x00000002, "R", MIXT_MONOSLIDER, 1}, + {0x00000030, "surr", MIXT_STEREOSLIDER, 2}, + {0x00000010, "LS", MIXT_MONOSLIDER, 1}, + {0x00000020, "RS", MIXT_MONOSLIDER, 1}, + {0x0000000c, "C/L", MIXT_STEREOSLIDER, 2}, + {0x00000004, "C", MIXT_MONOSLIDER, 1}, + {0x00000008, "LFE", MIXT_MONOSLIDER, 1}, + {0x000000c0, "center", MIXT_STEREOSLIDER, 2}, + {0x00000040, "LC", MIXT_MONOSLIDER, 1}, + {0x00000080, "RC", MIXT_MONOSLIDER, 1}, + {0x00000100, "surr", MIXT_STEREOSLIDER, 2}, + {0x00000600, "side", MIXT_STEREOSLIDER, 2}, + {0x00000200, "SL", MIXT_MONOSLIDER, 1}, + {0x00000400, "SR", MIXT_MONOSLIDER, 1}, + {0x00000800, "TC", MIXT_MONOSLIDER, 1}, + {0x00001000, "TFL", MIXT_MONOSLIDER, 1}, + {0x00002000, "TFC", MIXT_MONOSLIDER, 1}, + {0x00004000, "TFR", MIXT_MONOSLIDER, 1}, + {0x00008000, "TBL", MIXT_MONOSLIDER, 1}, + {0x00010000, "TBC", MIXT_MONOSLIDER, 1}, + {0x00020000, "TBR", MIXT_MONOSLIDER, 1}, + {0x00040000, "TFLC", MIXT_MONOSLIDER, 1}, + {0x00080000, "TFRC", MIXT_MONOSLIDER, 1}, + {0x00100000, "LLFE", MIXT_MONOSLIDER, 1}, + {0x00200000, "RLFE", MIXT_MONOSLIDER, 1}, + {0x00400000, "TSL", MIXT_MONOSLIDER, 1}, + {0x00800000, "TSR", MIXT_MONOSLIDER, 1}, + {0x01000000, "BC", MIXT_MONOSLIDER, 1}, + {0x02000000, "BLC", MIXT_MONOSLIDER, 1}, + {0x04000000, "BRC", MIXT_MONOSLIDER, 1}, + {0x80000000, "RD", MIXT_MONOSLIDER, 1}, + {0, NULL} +}; + +static int +count_source_controls (ossusb_devc * devc, int unit) +{ + int n = 0, nn, i; + usb_audio_unit_t *un; + unsigned char *d; + + if (unit <= 0 || unit >= devc->nunits) + return 0; + + un = &devc->units[unit]; + d = un->desc; + + if (un == NULL) + return 0; + + switch (un->typ) + { + case TY_MIXER: + case TY_SELECT: + nn = d[4]; + d += 5; + for (i = 0; i < nn; i++) + { + n += count_source_controls (devc, *d); + d++; + } + break; + + case TY_PROC: + case TY_EXT: + nn = d[6]; + d += 7; + for (i = 0; i < nn; i++) + { + n += count_source_controls (devc, *d); + d++; + } + break; + + default: + if (un->source <= 0 && un->source < devc->nunits) + { + n = count_source_controls (devc, un->source); + } + } + + if (!un->ctl_avail) + return n; + + return n + un->control_count; +} + +static int +count_target_controls (ossusb_devc * devc, int unit) +{ + int n = 0; + usb_audio_unit_t *un; + + if (unit <= 0 || unit >= devc->nunits) + return 0; + + un = &devc->units[unit]; + + if (un == NULL) + return 0; + + if (un->typ == TY_SELECT || un->typ == TY_MIXER) + { + n = 0; + } + else if (un->target <= 0 && un->target < devc->nunits) + { + n = count_target_controls (devc, un->target); + } + + if (!un->ctl_avail) + return n; + + return n + un->control_count; +} + +static int +follow_source_links (ossusb_devc * devc, usb_audio_unit_t * un) +{ + while (un->source > 0 && un->source < devc->nunits) + un = &devc->units[un->source]; + + return un->num; +} + +static int +follow_target_links (ossusb_devc * devc, usb_audio_unit_t * un) +{ + while (un->target > 0 && un->target < devc->nunits) + un = &devc->units[un->target]; + + return un->num; +} + +static void +add_controls_for_mixer (ossusb_devc * devc, usb_audio_unit_t * un, int group) +{ + int i, n; + unsigned char *d = un->desc; + + if (!un->ctl_avail) + return; + + n = d[4]; + d += 5; + + for (i = 0; i < n; i++) + if (*d > 0 && *d < devc->nunits) + { + int ref = follow_source_links (devc, &devc->units[*d]); + + { + + int ctl = new_ctl (devc, un, i, MIXT_MONOSLIDER, un->chmask); + UDB (cmn_err + (CE_CONT, "Add mixer control %d/%s\n", un->num, + devc->units[ref].name)); + if (mixer_ext_create_control (devc->mixer_dev, group, ctl, + mixer_func, MIXT_MONOSLIDER, + devc->units[ref].name, 255, + MIXF_READABLE | MIXF_WRITEABLE) < 0) + return; + } + d++; + } + + un->ctl_avail = 0; +} + +/*ARGSUSED*/ +static void +add_controls_for_proc (ossusb_devc * devc, usb_audio_unit_t * un, int group) +{ + int i, n; + unsigned char *d = un->desc; + + if (!un->ctl_avail) + return; + + n = d[6]; + d += 7; + + for (i = 0; i < n; i++) + if (*d > 0 && *d < devc->nunits) + { + d++; + } + + un->ctl_avail = 0; +} + +static void +add_controls_for_selector (ossusb_devc * devc, usb_audio_unit_t * un, + int group) +{ + static oss_mixer_enuminfo ent; + char *s; + int i, n, err; + unsigned char *d = un->desc; + int ctl = new_ctl (devc, un, 0, MIXT_ENUM, 0); + + if (!un->ctl_avail) + return; + + n = d[4]; + d += 5; + UDB (cmn_err (CE_CONT, "Add selector control %d/%s\n", un->num, un->name)); + if ((err = mixer_ext_create_control (devc->mixer_dev, group, + ctl, mixer_func, + MIXT_ENUM, + un->name, n, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return; + + memset (&ent, 0, sizeof (ent)); + + s = ent.strings; + + for (i = 0; i < n; i++) + if (*d > 0 && *d < devc->nunits) + { + int ref = follow_source_links (devc, &devc->units[*d]); + d++; + + if (s > ent.strings) + *s++ = '|'; + strcpy (s, devc->units[ref].name); + s += strlen (s); + } + + s = ent.strings; + for (i = 0; i < strlen (s); i++) + { + if (s[i] == ' ') + s[i] = '_'; + if (s[i] == '|') + s[i] = ' '; + } + + ent.dev = devc->mixer_dev; + ent.ctrl = err; + ent.nvalues = 0; + + mixer_ext_set_enum (&ent); + + un->ctl_avail = 0; +} + +/*ARGSUSED*/ +static void +add_multich_volumes (ossusb_devc * devc, usb_audio_unit_t * un, int group, + int fea, int mask, unsigned int *feature_mask) +{ + int i; + + for (i = 0; mask != 0 && chmasks[i].mask != 0; i++) + { + int m = chmasks[i].mask; + int ctl; + usb_control_t *c; + + if (!(mask & m)) + continue; + + ctl = new_ctl (devc, un, fea, chmasks[i].type, chmasks[i].mask); + c = &devc->controls[ctl]; + + UDB (cmn_err + (CE_CONT, "Add multich feature control %d/%s\n", un->num, + chmasks[i].name)); + if (mixer_ext_create_control (devc->mixer_dev, group, ctl, mixer_func, + chmasks[i].type, chmasks[i].name, + c->scale, + MIXF_READABLE | MIXF_WRITEABLE) < 0) + return; + + mask &= ~m; + } + + if (mask) + cmn_err (CE_CONT, "Warning! Unsupported channels (%02x)\n", mask); +} + +static void +translate_feature_mask_usb2 (ossusb_devc *devc, usb_audio_unit_t * un, unsigned int *feature_mask) +{ + int i, n, c; + unsigned char *d = un->desc; + + n = 4; + d = d + 5 + n; // Skip the global mask + +/* + * USB 2.0 uses 2 bits for each control + * 01b means that the control is read only and 11b means it's RW + */ + for (c = 0; c < un->channels; c++) + { + unsigned int mask; + unsigned char *p = d + c * n; + + mask = p[3] | + (p[2] << 8) | + (p[1] << 16) | + (p[0] << 24); + + for (i = 0; i < n * 4; i++) + if ((mask & (3 << 2*i)) && (un->ctl_avail & (1 << i))) + { + feature_mask[i] |= 1 << c; + } + } +} + +static void +translate_feature_mask (ossusb_devc *devc, usb_audio_unit_t * un, unsigned int *feature_mask) +{ + int i, n, c; + unsigned char *d = un->desc; + + if (devc->usb_version > 1) + { + translate_feature_mask_usb2(devc, un, feature_mask); + return; + } + + n = d[5]; + if (n < 1 || n > 2) + { + cmn_err (CE_CONT, "Bad feature mask size %d\n", n); + return; + } + + d = d + 6 + n; // Skip the global mask + + for (c = 0; c < un->channels; c++) + { + int mask = d[c * n]; + if (n > 1) + mask |= d[c * n + 1]; + + for (i = 0; i < n * 8; i++) + if ((mask & (1 << i)) && (un->ctl_avail & (1 << i))) + { + feature_mask[i] |= 1 << c; + } + } +} + +static void +add_controls_for_feature (ossusb_devc * devc, usb_audio_unit_t * un, + int group) +{ + int i; + unsigned int feature_mask[16], global_mask; + + if (!un->ctl_avail) + return; + +// Handle the global controls first + + global_mask = un->desc[6]; + if (un->desc[5] > 0) + global_mask |= un->desc[7] << 8; + + for (i = 0; i < 11; i++) + if (un->ctl_avail & (1 << i) && (global_mask & (1 << i))) + { + char *name = get_feature_name (i); + int type = get_feature_type (i); + int max; + char tmp[16]; + int ctl; + + ctl = new_ctl (devc, un, i, type, 0); + strcpy (tmp, name); + if (type == MIXT_SLIDER) + max = devc->controls[ctl].max - devc->controls[ctl].min; + else + max = 1; + + if (max > 255) + max = 255; + + UDB (cmn_err + (CE_CONT, "Add (global) feature control %d:%s, max=%d\n", + un->num, name, max)); + if (mixer_ext_create_control (devc->mixer_dev, group, ctl, + mixer_func, type, tmp, max, + MIXF_READABLE | MIXF_WRITEABLE) < 0) + return; + //un->ctl_avail &= ~(1 << i); + } + +// Handle the channelized controls + + if (un->ctl_avail == 0) /* Nothing left */ + return; + + // Translate the channel/feature availability matrix + + memset (feature_mask, 0, sizeof (feature_mask)); + translate_feature_mask (devc, un, feature_mask); + + for (i = 0; i < 16; i++) + if (feature_mask[i]) + { + char *name = get_feature_name (i); + int type = get_feature_type (i); + int max, instances = 0; + + int nc = 0, j; + + for (j = 0; j < 16; j++) + if (feature_mask[i] & (1 << j)) + nc = j + 1; + + if (type == MIXT_SLIDER) + { + if (nc == 1) + { + type = MIXT_MONOSLIDER; + instances = 1; + } + else if (nc == 2) + { + type = MIXT_STEREOSLIDER; + instances = 1; + } + else + { + type = MIXT_MONOSLIDER; + if (i == 1) /* "volume" */ + instances = nc; + else + { + instances = 0; + type = MIXT_MONOSLIDER; + } + } + max = 255; + } + else + max = 2; + + if (instances && (instances > 1 || feature_mask[i] > 0x0003)) + { + int g; + char tmp[16]; + strcpy (tmp, name); + UDB (cmn_err (CE_CONT, "Add feature group %s\n", tmp)); + if ((g = + mixer_ext_create_group (devc->mixer_dev, group, tmp)) < 0) + return; + + add_multich_volumes (devc, un, g, i, feature_mask[i], + feature_mask); + } + else + { + char tmp[16]; + int ctl = new_ctl (devc, un, i, type, un->chmask); + strcpy (tmp, name); + max = devc->controls[ctl].max - devc->controls[ctl].min; + if (max > 255) + max = 255; + + UDB (cmn_err + (CE_CONT, "Add feature control %d:%s\n", un->num, name)); + if (mixer_ext_create_control (devc->mixer_dev, group, ctl, + mixer_func, type, tmp, max, + MIXF_READABLE | MIXF_WRITEABLE) < 0) + return; + } + } + + un->ctl_avail = 0; // All done (hope so) +} + +static void +traverse_source_controls (ossusb_devc * devc, usb_audio_unit_t * un, + int group) +{ + unsigned char *d; + + d = un->desc; + + if (un->typ == TY_MIXER) + { + add_controls_for_mixer (devc, un, group); + return; + } + + if (un->typ == TY_PROC) + { + add_controls_for_proc (devc, un, group); + + if (d[6] > 1) /* More than 1 sources */ + return; + } + + if (un->typ == TY_SELECT) + { + add_controls_for_selector (devc, un, group); + return; + } + + if (un->source > 0 && un->source < devc->nunits) + { + traverse_source_controls (devc, &devc->units[un->source], group); + } + + if (un->typ == TY_FEAT) + { + add_controls_for_feature (devc, un, group); + } +} + +static void +traverse_target_controls (ossusb_devc * devc, usb_audio_unit_t * un, + int group) +{ + unsigned char *d; + + + d = un->desc; + + if (un->typ == TY_SELECT) + { + add_controls_for_selector (devc, un, group); + } + + if (un->typ == TY_PROC || un->typ == TY_EXT) + if (d[6] > 1) // More than 1 input pins + return; + + if (un->typ == TY_MIXER) + { +#if 1 + add_controls_for_mixer (devc, un, group); +#endif + } + + if (un->target > 0 && un->target < devc->nunits) + traverse_target_controls (devc, &devc->units[un->target], group); + + if (un->typ == TY_FEAT) + { + add_controls_for_feature (devc, un, group); + } +} + +void +ossusb_create_altset_control (int dev, int portc_num, int nalt, char *name) +{ + int ctl; + ossusb_devc *devc = mixer_devs[dev]->devc; + char *as = NULL; + int default_altsetting; + unsigned int altsetting_mask; + + if (nalt < 3) /* Only one alternative setting (plus "OFF") available */ + return; + if ((as = + udi_usbdev_get_altsetting_labels (devc->mixer_usbdev, portc_num, + &default_altsetting, + &altsetting_mask)) != NULL) + { + if (altsetting_mask != 0) + { + /* + * Check if more than one of them are enabled. + */ + + int i, n = 0; + + for (i = 1; i < nalt; i++) + if (altsetting_mask & (1 << i)) + n++; + + if (n < 2) /* No */ + return; + } + } + + if ((ctl = mixer_ext_create_control (dev, 0, + portc_num, ossusb_change_altsetting, + MIXT_ENUM, + name, nalt, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return; + + if (as != NULL) /* Use custom labels */ + { + mixer_ext_set_strings (dev, ctl, as, 0); + if (altsetting_mask != 0) + { + oss_mixext *ext; + int i; + + ext = mixer_find_ext (dev, ctl); + + memset (ext->enum_present, 0, sizeof (ext->enum_present)); + for (i = 0; i < nalt; i++) + if (altsetting_mask & (1 << i)) + ext->enum_present[i / 8] |= (1 << (i % 8)); + } + + if (default_altsetting > 0) + ossusb_change_altsetting (dev, portc_num, SNDCTL_MIX_WRITE, + default_altsetting); + } + else + mixer_ext_set_strings (dev, ctl, + "OFF 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19", + 0); + +} + +static int +usb_mix_init (int dev) +{ + ossusb_devc *devc; + int i, group; + + devc = mixer_devs[dev]->devc; + + for (i = 0; i < devc->nunits; i++) + { + usb_audio_unit_t *un = &devc->units[i]; + + if (un->typ == TY_OUTPUT) + { + if (count_source_controls (devc, un->source) == 0) + { + continue; + } + + if ((group = mixer_ext_create_group (dev, 0, un->name)) < 0) + return group; + + traverse_source_controls (devc, un, group); + continue; + } + } + + for (i = 0; i < devc->nunits; i++) + { + usb_audio_unit_t *un = &devc->units[i]; + + if (un->typ == TY_INPUT) + { + if (count_target_controls (devc, un->target) == 0) + { + continue; + } + + if ((group = mixer_ext_create_group (dev, 0, un->name)) < 0) + return group; + + traverse_target_controls (devc, un, group); + un->ctl_avail = 0; + continue; + } + } + + return 0; +} + +static char * +check_feature (usb_audio_unit_t * un, unsigned char *mask, int n) +{ + if (mask[n / 8] & (1 << (n % 8))) + { + un->control_count++; + + return get_feature_name (n); + } + + return NULL; +} + +/*ARGSUSED*/ +static int +usb_mixer_ioctl (int dev, int audiodev, unsigned int cmd, ioctl_arg arg) +{ + ossusb_devc *devc = mixer_devs[dev]->hw_devc; + + if (devc->disabled) + return OSS_EPIPE; + + switch (cmd) + { + case SOUND_MIXER_READ_DEVMASK: + return *arg = devc->devmask | devc->recmask; + case SOUND_MIXER_READ_RECMASK: + return *arg = devc->recmask; + case SOUND_MIXER_READ_RECSRC: + return *arg = devc->recsrc; + case SOUND_MIXER_READ_STEREODEVS: + return *arg = devc->stereodevs; + } + + return *arg = 0; +} + +static mixer_driver_t usb_mixer_driver = { + usb_mixer_ioctl +}; + +static int +init_mixer (ossusb_devc * devc) +{ + if ((devc->mixer_dev = oss_install_mixer (OSS_MIXER_DRIVER_VERSION, + devc->osdev, + devc->osdev, + devc->dev_name, + &usb_mixer_driver, + sizeof (mixer_driver_t), + devc)) >= 0) + { + char mxname[20]; + + sprintf (mxname, "USB_%d", ndevs); + + devc->levels = load_mixer_volumes (mxname, NULL, 1); + mixer_devs[devc->mixer_dev]->hw_devc = devc; + mixer_ext_set_init_fn (devc->mixer_dev, usb_mix_init, MAX_CONTROLS); + } + + return 1; +} + +static usb_audio_unit_t * +setup_unit (ossusb_devc * devc, int unit, char *name, unsigned char *desc, + int desclen, int typ) +{ + usb_audio_unit_t *un; + + if (unit < 0 || unit >= MAX_UNIT) + { + cmn_err (CE_WARN, "Bad unit number %d\n", unit); + return NULL; + } + + if (typ >= sizeof (devc->unit_counters)) + { + cmn_err (CE_WARN, "Bad unit type %d\n", typ); + return NULL; + } + + devc->unit_counters[typ]++; + + if (unit >= devc->nunits) + devc->nunits = unit + 1; + + un = &devc->units[unit]; + + un->num = unit; + un->typ = typ; + un->desc = desc; + un->desclen = desclen; + un->mixnum = -1; + un->channels = 2; + un->chmask = 0x03; + strcpy (un->name, name); + + return un; +} + +static char * +get_terminal_id (int type, char *def, int typ, int *mn) +{ + int dummy, *mixnum; + + if (mn != NULL) + mixnum = mn; + else + mixnum = &dummy; + switch (type) + { + case 0x0101: + if (typ == TY_INPUT) + { + *mixnum = SOUND_MIXER_PCM; + return "play"; + } + if (typ == TY_OUTPUT) + { + *mixnum = SOUND_MIXER_RECLEV; + return "rec"; + } + break; + case 0x0301: + *mixnum = SOUND_MIXER_VOLUME; + return "output"; + case 0x0302: + *mixnum = SOUND_MIXER_VOLUME; + return "headph"; + case 0x0307: + return "LFE"; + + case 0x0401: + return "handset"; + case 0x0402: + return "headset"; + + case 0x0403: + case 0x0404: + case 0x0405: + case 0x0500: + case 0x0501: + case 0x0502: + case 0x0504: + return "phone"; + break; + + case 0x0600: + *mixnum = SOUND_MIXER_LINE1; + return "aux"; + case 0x0601: + *mixnum = SOUND_MIXER_LINE2; + return "aux"; + case 0x0602: + return "digital"; + case 0x0603: + *mixnum = SOUND_MIXER_LINE; + return "line"; + case 0x0604: + *mixnum = SOUND_MIXER_SPEAKER; + return "pc_in"; + case 0x0605: + *mixnum = SOUND_MIXER_DIGITAL1; + if (typ == TY_INPUT) + return "spdin"; + else + return "spdout"; + case 0x0606: + return "da_stream"; + case 0x0607: + return "DV_audio"; + + case 0x0701: + return "noise"; + case 0x0702: + return "eq_noise"; + case 0x0703: + *mixnum = SOUND_MIXER_CD; + return "cd"; + case 0x0704: + return "dat"; + case 0x0706: + return "md"; + case 0x0707: + return "tape"; + case 0x0708: + return "phono"; + case 0x0709: + *mixnum = SOUND_MIXER_VIDEO; + return "vcr"; + case 0x070b: + return "dvd"; + case 0x070c: + return "tv"; + case 0x070d: + return "sat"; + case 0x070e: + return "cable"; + case 0x0710: + *mixnum = SOUND_MIXER_RADIO; + return "radio"; + case 0x0711: + *mixnum = SOUND_MIXER_RADIO; + return "xmitter"; + case 0x0712: + return "mtrack"; + case 0x0713: + *mixnum = SOUND_MIXER_SYNTH; + return "synth"; + } + + if (type < 0x201) // Unknown type + return def; + + if (type < 0x300) + { + *mixnum = SOUND_MIXER_MIC; + return "mic"; + } + + return def; +} + +static void +setup_legacy_mixer (ossusb_devc * devc) +{ + int x; + +// Set up the recording selector + + for (x = 1; x < devc->nunits; x++) + { + usb_audio_unit_t *un = &devc->units[x]; + int i, n; + unsigned char *d = un->desc; + + if (un->typ != TY_SELECT || strcmp (un->name, "rec.src") != 0) + continue; + + if (!un->ctl_avail) + continue; + + devc->rec_unit = x; + un->ctl_avail = 0; + + devc->num_recdevs = n = d[4]; + d += 5; + for (i = 0; i < n; i++) + { + int ref = follow_source_links (devc, &devc->units[*d]); + int mask; + + d++; + + if (ref < 1 || ref >= devc->nunits || devc->units[ref].mixnum == -1) + continue; + + mask = (1 << devc->units[ref].mixnum); + if (devc->recmask & mask) // Duplicate + continue; + UDB (cmn_err + (CE_CONT, "Recsrc %d, num=%d, mask %x\n", i, ref, mask)); + + devc->recdevs[i] = mask; + + devc->recmask |= mask; + if (devc->recsrc == 0) + devc->recsrc = mask; + } + + break; + } + +// Set up the legacy mixer controls + + for (x = 1; x < devc->nunits; x++) + { + usb_audio_unit_t *un = &devc->units[x]; + int mixnum; + unsigned char *d = un->desc; + + if (!(un->ctl_avail & 0x02)) + continue; + + if (un->typ == TY_FEAT && d[6] & 0x02) // Volume control + { + int t; + mixnum = un->mixnum; + + if (mixnum == -1) + { + t = follow_target_links (devc, un); + if (t && devc->units[t].mixnum != -1) + mixnum = devc->units[t].mixnum; + else + { + t = follow_source_links (devc, un); + if (t && devc->units[t].mixnum != -1) + mixnum = devc->units[t].mixnum; + } + } + + if (mixnum == -1 || un->channels > 2) + continue; + UDB (cmn_err (CE_CONT, "Unit %d is volume %d\n", x, mixnum)); + un->ctl_avail &= ~0x02; // Reserve the volume slider + + devc->devmask |= (1 << mixnum); + if (un->channels == 2) + devc->stereodevs |= (1 << mixnum); + + continue; + } + } + +} + +static char * +get_processor_type (int t) +{ + switch (t) + { + case 0x01: + return "upmix"; + case 0x02: + return "prologic"; + case 0x03: + return "3D"; + case 0x04: + return "reverb"; + case 0x05: + return "chorus"; + case 0x06: + return "compress"; + default: + return "proc"; + } +} + +static void +parse_processing_unit (ossusb_devc * devc, unsigned char *d, int l) +{ + usb_audio_unit_t *un; + char *name; + + name = get_processor_type (d[4]); + + if ((un = setup_unit (devc, d[3], name, d, l, TY_PROC)) == NULL) + return; + un->subtyp = d[4]; + un->ctl_avail = 1; + + if (un->subtyp == 1) // Upmix/downmix unit + { + un->channels = d[8]; + un->chmask = ossusb_get_int (&d[9], 2); + } +} + +static int +get_feature_mask (unsigned char *d, int channels) +{ + int i, n, mask = 0, v; + n = d[5]; + + for (i = 0; i <= channels; i++) + { + v = d[6 + i * n]; + if (n > 1) + v |= d[6 + i * n + 1] << 8; + + mask |= v; + } + + return mask; +} + +#if 1 +static void +mixer_dump (ossusb_devc * devc) +{ + int i, j, c; + usb_audio_unit_t *un; + + for (i = 1; i < devc->nunits; i++) + { + un = &devc->units[i]; + + if (un->typ == TY_FEAT) + { + cmn_err (CE_CONT, "Unit %d\n", un->num); + + for (j = 0; j < 8; j++) + { + for (c = 0; c < 7; c++) + { + unsigned char buf[2]; + int len; + + buf[0] = buf[1] = 0; + len = udi_usb_rcv_control_msg (devc->mixer_usbdev, 0, // endpoint + GET_CUR, USB_RECIP_INTERFACE | USB_TYPE_CLASS, // rqtype + ((j) << 8) | (c), // value + (un->num << 8), // index + buf, // buffer + 2, // buflen + OSS_HZ * 2); + + if (len < 0) + continue; + cmn_err (CE_CONT, " Feature %d/%d, ch %d: ", un->num, j, + c); + cmn_err (CE_CONT, "%02x%02x ", buf[0], buf[1]); + + if (len == 1) + cmn_err (CE_CONT, "(%d) ", (signed char) buf[0]); + else + cmn_err (CE_CONT, "(%d) ", + (signed short) (buf[0] | (buf[1] << 8))); + + buf[0] = buf[1] = 0; + len = udi_usb_rcv_control_msg (devc->mixer_usbdev, 0, // endpoint + GET_MIN, USB_RECIP_INTERFACE | USB_TYPE_CLASS, // rqtype + ((j) << 8) | (c), // value + (un->num << 8), // index + buf, // buffer + 2, // buflen + OSS_HZ * 2); + + if (len >= 0) + cmn_err (CE_CONT, "Min=%02x%02x ", buf[0], buf[1]); + if (len == 1) + cmn_err (CE_CONT, "(%d) ", (signed char) buf[0]); + else if (len == 2) + cmn_err (CE_CONT, "(%d) ", + (signed short) (buf[0] | (buf[1] << 8))); + + buf[0] = buf[1] = 0; + len = udi_usb_rcv_control_msg (devc->mixer_usbdev, 0, // endpoint + GET_MAX, USB_RECIP_INTERFACE | USB_TYPE_CLASS, // rqtype + ((j) << 8) | (c), // value + (un->num << 8), // index + buf, // buffer + 2, // buflen + OSS_HZ * 2); + + if (len >= 0) + cmn_err (CE_CONT, "max=%02x%02x ", buf[0], buf[1]); + if (len == 1) + cmn_err (CE_CONT, "(%d) ", (signed char) buf[0]); + else if (len == 2) + cmn_err (CE_CONT, "(%d) ", + (signed short) (buf[0] | (buf[1] << 8))); + + cmn_err (CE_CONT, "\n"); + } + } + } + } +} +#endif + +/*ARGSUSED*/ +static ossusb_devc * +ossusb_init_audioctl (ossusb_devc * devc, udi_usb_devc * usbdev, int inum, + int reinit) +{ + int desc_len; + unsigned char *desc, *d; + int i, l, n = 0, p, x, mixnum = -1; + char *name; + usb_audio_unit_t *un; + + /* nsettings = udi_usbdev_get_num_altsettings (usbdev); */ + devc->mixer_usbdev = usbdev; + + if (!init_mixer (devc)) + return NULL; + + desc = udi_usbdev_get_altsetting (usbdev, 0, &desc_len); + + if (desc == NULL) + { + cmn_err (CE_CONT, "Can't read interface descriptors\n"); + return NULL; + } + + p = 0; + while (p < desc_len) + { + d = desc + p; + + l = *d; + if (usb_trace > 1) + { + char str[256], *s = str; + sprintf (s, "Control desc(%d): ", p); + s += strlen (s); + for (i = 0; i < l; i++) + { + sprintf (s, "%02x ", d[i]); + s += strlen (s); + } + cmn_err (CE_CONT, "%s\n", str); + } + +#define CASE(x) case x:cmn_err(CE_CONT, #x "\n"); break; + + if (d[1] == CS_INTERFACE) + switch (d[2]) + { + case AC_HEADER: + if (usb_trace) + { + cmn_err (CE_CONT, " Audio control interface header\n"); + n = d[7]; + cmn_err (CE_CONT, " %d related streaming interfaces: ", + n); + for (i = 0; i < n; i++) + { + cmn_err (CE_CONT, "%d ", d[8 + i]); + } + cmn_err (CE_CONT, "\n"); + } + break; + + case AC_INPUT_TERMINAL: + name = + get_terminal_id (ossusb_get_int (&d[4], 2), "in", TY_INPUT, + &mixnum); + if ((un = setup_unit (devc, d[3], name, d, l, TY_INPUT)) == NULL) + return NULL; + un->mixnum = mixnum; + un->channels = d[7]; + un->chmask = ossusb_get_int (&d[8], 2); + break; + + case AC_OUTPUT_TERMINAL: + name = + get_terminal_id (ossusb_get_int (&d[4], 2), "out", TY_OUTPUT, + &mixnum); + if ((un = setup_unit (devc, d[3], name, d, l, TY_OUTPUT)) == NULL) + return NULL; + un->mixnum = mixnum; + break; + + case AC_MIXER_UNIT: + if ((un = setup_unit (devc, d[3], "mix", d, l, TY_MIXER)) == NULL) + return NULL; + { + // Check if there are any visible controls + + int mask = 0, nn; + + n = d[4]; // # of input pins + d += 5 + n; + nn = *d; // # of channels + d += 4; // Seek to bmControls + + n = (n * nn + 7) / 8; + + for (i = 0; i < n; i++) + mask |= d[i]; + + un->ctl_avail = mask; + } + break; + + case AC_SELECTOR_UNIT: + if ((un = + setup_unit (devc, d[3], "src", d, l, TY_SELECT)) == NULL) + return NULL; + un->ctl_avail = 1; + break; + + case AC_FEATURE_UNIT: + if ((un = setup_unit (devc, d[3], "fea", d, l, TY_FEAT)) == NULL) + return NULL; + un->ctl_avail = get_feature_mask (d, 2); /* For now */ + break; + + case AC_PROCESSING_UNIT: + parse_processing_unit (devc, d, l); + break; + + case AC_EXTENSION_UNIT: + if ((un = setup_unit (devc, d[3], "ext", d, l, TY_EXT)) == NULL) + return NULL; + un->ctl_avail = 1; + + break; + + } + + p += l; + } + +// Make sure the unit names are unique */ + + for (x = 1; x < devc->nunits; x++) + { + usb_audio_unit_t *un = &devc->units[x]; + int n = 0; + + if (un->typ != TY_SELECT) + for (i = x + 1; i < devc->nunits; i++) + if (strcmp (devc->units[i].name, un->name) == 0) + n++; + + if (n > 0) + { + char tmpname[16]; + strcpy (tmpname, un->name); + n = 1; + + for (i = x; i < devc->nunits; i++) + if (strcmp (devc->units[i].name, tmpname) == 0) + { + if (n > 1) + sprintf (devc->units[i].name, "%s%d", tmpname, n); + n++; + } + } + +// Make sure the mixer control numbers are unique too + + n = 0; + if (un->mixnum != -1) + for (i = x + 1; i < devc->nunits; i++) + if (devc->units[i].mixnum == un->mixnum) + n++; + + if (n > 0) + for (i = x + 1; i < devc->nunits; i++) + if (devc->units[i].mixnum == un->mixnum) + { + usb_audio_unit_t *uu = &devc->units[i]; + + switch (uu->mixnum) + { + case SOUND_MIXER_PCM: + uu->mixnum = SOUND_MIXER_ALTPCM; + break; + case SOUND_MIXER_LINE: + uu->mixnum = SOUND_MIXER_LINE1; + break; + case SOUND_MIXER_LINE1: + uu->mixnum = SOUND_MIXER_LINE2; + break; + case SOUND_MIXER_LINE2: + uu->mixnum = SOUND_MIXER_LINE3; + break; + case SOUND_MIXER_DIGITAL1: + uu->mixnum = SOUND_MIXER_DIGITAL2; + break; + case SOUND_MIXER_DIGITAL2: + uu->mixnum = SOUND_MIXER_DIGITAL3; + break; + default: + uu->mixnum = -1; + } + } + + } + +// Handle output selector names + + + for (x = 1; x < devc->nunits; x++) + { + usb_audio_unit_t *un = &devc->units[x]; + int n; + + if (un->typ != TY_OUTPUT) + continue; + + n = un->desc[7]; // Source ID + if (n < 0 || n >= devc->nunits) + continue; + + if (devc->units[n].target == 0) + devc->units[n].target = x; + + if (devc->units[n].typ == TY_SELECT && usb_mixerstyle == 0) + sprintf (devc->units[n].name, "%s.src", un->name); + } + +// Find out the sources + for (x = 1; x < devc->nunits; x++) + { + usb_audio_unit_t *un = &devc->units[x]; + + d = un->desc; + l = un->desclen; + + switch (un->typ) + { + case TY_INPUT: + break; + + case TY_OUTPUT: + un->source = d[7]; + break; + + case TY_SELECT: + un->source = d[5]; + n = d[4]; + for (i = 0; i < n; i++) + if (d[i + 5] > 0 && d[i + 5] <= devc->nunits) + if (devc->units[d[i + 5]].target == 0) + devc->units[d[i + 5]].target = x; + break; + + case TY_MIXER: + n = d[4]; + un->control_count = n; + un->source = d[5]; + for (i = 0; i < n; i++) + if (d[i + 5] > 0 && d[i + 5] <= devc->nunits) + if (devc->units[d[i + 5]].target == 0) + devc->units[d[i + 5]].target = x; + + d += n + 5; + un->channels = *d++; + un->chmask = ossusb_get_int (d, 2); + break; + + case TY_FEAT: + un->source = d[4]; + if (d[4] > 0 && d[4] <= devc->nunits) + if (devc->units[d[4]].target == 0) + devc->units[d[4]].target = x; + break; + + //case TY_EXT: + case TY_PROC: + n = d[6]; + un->source = d[7]; + for (i = 0; i < n; i++) + if (d[i + 7] > 0 && d[i + 7] <= devc->nunits) + if (devc->units[d[i + 7]].target == 0) + devc->units[d[i + 7]].target = x; + break; + } + } + +// Trace the channel config + + for (x = 1; x < devc->nunits; x++) + { + usb_audio_unit_t *un = &devc->units[x], *uu; + int ref; + + if (un->typ == TY_INPUT || un->typ == TY_MIXER) + continue; + + if (un->typ == TY_PROC && un->subtyp == 1) // Upmix/downmix unit + continue; + + ref = follow_source_links (devc, un); + uu = &devc->units[ref]; + + un->channels = uu->channels; + un->chmask = uu->chmask; + } + +// Handle feature channels + for (x = 1; x < devc->nunits; x++) + { + usb_audio_unit_t *un = &devc->units[x]; + + if (un->num == 0 || un->typ != TY_FEAT) + continue; + + d = un->desc; + l = un->desclen; + + un->ctl_avail = get_feature_mask (d, un->channels); + } + +// Final checks + for (x = 1; x < devc->nunits; x++) + { + usb_audio_unit_t *un = &devc->units[x]; + int j; + + if (un->num == 0) + { + //cmn_err(CE_CONT, "Skipped undefined control %d\n", x); + continue; + } + d = un->desc; + l = un->desclen; + + switch (un->typ) + { + case TY_SELECT: + n = d[4]; + un->control_count = n; + d += 5; + break; + + case TY_MIXER: + n = d[4]; + un->control_count = n; + d += 5; + break; + + case TY_FEAT: + n = d[5]; + d += 6; + for (i = 0; i < n * 8; i++) + name = check_feature (un, d, i); + for (j = 1; j <= un->channels; j++) + { + for (i = 0; i < n * 8; i++) + name = check_feature (un, d + j * n, i); + } + break; + + } + } + +#if 0 +// Debugging + if (usb_trace) + for (x = 1; x < devc->nunits; x++) + { + usb_audio_unit_t *un = &devc->units[x]; + int j; + int ref = 0; + + if (un->num == 0) + { + //cmn_err(CE_CONT, "Skipped undefined control %d\n", x); + continue; + } + d = un->desc; + l = un->desclen; + // ossusb_dump_desc (d, l); + cmn_err (CE_CONT, "%2d: %s ", un->num, un->name); + if (un->mixnum != -1) + cmn_err (CE_CONT, "mix=%d ", un->mixnum); + if (un->source) + cmn_err (CE_CONT, "source=%d ", un->source); + if (un->target) + cmn_err (CE_CONT, "target=%d ", un->target); + cmn_err (CE_CONT, "ch=%d/%x ", un->channels, un->chmask); + + switch (un->typ) + { + case TY_INPUT: + cmn_err (CE_CONT, "Input terminal type: %04x ", + ossusb_get_int (&d[4], 2)); + cmn_err (CE_CONT, "Associated output 0x%02x ", d[6]); + cmn_err (CE_CONT, "#chn %d ", d[7]); + cmn_err (CE_CONT, "chconf %04x ", ossusb_get_int (&d[8], 2)); + if (d[10]) + cmn_err (CE_CONT, "chname# %d ", d[10]); + if (d[11]) + cmn_err (CE_CONT, "terminalname# %d (%s) ", d[11], + udi_usbdev_get_string (usbdev, d[11])); + break; + + case TY_OUTPUT: + cmn_err (CE_CONT, "Output terminal type: %04x ", + ossusb_get_int (&d[4], 2)); + cmn_err (CE_CONT, "Associated input 0x%02x ", d[6]); + cmn_err (CE_CONT, "sourceid %d ", d[7]); + if (d[8]) + cmn_err (CE_CONT, "terminalname# %d ", d[8]); + break; + + case TY_SELECT: + n = d[4]; + d += 5; + cmn_err (CE_CONT, "%d input pins (", n); + for (i = 0; i < n; i++) + { + ref = follow_source_links (devc, &devc->units[*d]); + cmn_err (CE_CONT, "%d/%s ", *d, devc->units[ref].name); + d++; + } + cmn_err (CE_CONT, ") "); + break; + + case TY_MIXER: + n = d[4]; + d += 5; + cmn_err (CE_CONT, "%d inputs (", n); + for (i = 0; i < n; i++) + { + ref = follow_source_links (devc, &devc->units[*d]); + cmn_err (CE_CONT, "%d/%s ", *d, devc->units[ref].name); + d++; + } + cmn_err (CE_CONT, ") "); + break; + + case TY_FEAT: + //ossusb_dump_desc(d, l); + cmn_err (CE_CONT, "Source %d:%s ", d[4], devc->units[d[4]].name); + n = d[5]; + d += 6; + cmn_err (CE_CONT, "main (", n); + for (i = 0; i < n * 8; i++) + if ((name = check_feature (un, d, i)) != NULL) + cmn_err (CE_CONT, "%s ", name); + cmn_err (CE_CONT, ") "); + for (j = 1; j <= un->channels; j++) + { + cmn_err (CE_CONT, "ch %d (", j); + for (i = 0; i < n * 8; i++) + if ((name = check_feature (un, d + j * n, i)) != NULL) + cmn_err (CE_CONT, "%s ", name); + cmn_err (CE_CONT, ") "); + } + break; + + case TY_PROC: + cmn_err (CE_CONT, "subtype %x Sources/%d) (", un->subtyp, n); + n = d[6]; + d += 7; + cmn_err (CE_CONT, "%d ", *d); + d++; + cmn_err (CE_CONT, ") "); + break; + + case TY_EXT: + cmn_err (CE_CONT, "Extension unit %02x%02x ", d[5], d[4]); + break; + + } + + cmn_err (CE_CONT, "\n"); + } +#endif + + if (usb_mixerstyle == 0) + setup_legacy_mixer (devc); + touch_mixer (devc->mixer_dev); + + if (usb_trace) + mixer_dump (devc); + + return devc; +} + +static ossusb_devc * +find_devc (char *devpath, int vendor, int product) +{ + int i; + + for (i = 0; i < ndevs; i++) + { + if (devc_list[i]->vendor == vendor && devc_list[i]->product == product) + if (strcmp (devc_list[i]->devpath, devpath) == 0) + { + UDB (cmn_err (CE_CONT, "Another instance of '%s'\n", devpath)); + return devc_list[i]; + } + } + + return NULL; +} + +static void +ossusb_device_disconnect (void *d) +{ + ossusb_devc *devc = d; + int i; + + if (devc == NULL) + { + cmn_err (CE_WARN, "oss_usb_device_disconnect: devc==NULL\n"); + return; + } + + if (devc->unload_func) + { + devc->unload_func (devc); + return; + } + + devc->disabled = 1; + + for (i = 0; i < devc->num_audio_engines; i++) + { + int dev; + + dev = devc->portc[i].audio_dev; + + if (dev < 0 || dev >= num_audio_engines) + continue; + + audio_engines[dev]->enabled = 0; + + if (devc->mixer_dev >= 0) + mixer_devs[devc->mixer_dev]->enabled = 0; + } + +} + +static void * +ossusb_device_attach (udi_usb_devc * usbdev, oss_device_t * osdev) +{ + ossusb_devc *devc; + char *devpath; + int inum; + int old = 1; + int i; + int class, subclass; + int vendor, product, version; + + devpath = udi_usbdev_get_devpath (usbdev); + inum = udi_usbdev_get_inum (usbdev); + class = udi_usbdev_get_class (usbdev); + subclass = udi_usbdev_get_subclass (usbdev); + vendor = udi_usbdev_get_vendor (usbdev); + product = udi_usbdev_get_product (usbdev); + version = udi_usbdev_get_usb_version (usbdev); + + if ((devc = find_devc (devpath, vendor, product)) == NULL) + { + old = 0; + + if (ndevs >= MAX_DEVC) + { + cmn_err (CE_CONT, "Too many USB audio devices\n"); + return NULL; + } + + if ((devc = PMALLOC (osdev, sizeof (*devc))) == NULL) + { + cmn_err (CE_CONT, "Out of memory\n"); + return NULL; + } + memset (devc, 0, sizeof (*devc)); + devc->mixer_dev = -1; + + devc->osdev = osdev; + osdev->devc = devc; + + MUTEX_INIT (devc->osdev, devc->mutex, MH_DRV); + + devc->vendor = vendor; + devc->product = product; + devc->usb_version = version; + devc->dev_name = udi_usbdev_get_name (usbdev); + + strcpy (devc->devpath, udi_usbdev_get_devpath (usbdev)); + + devc_list[ndevs++] = devc; + } + else + { + devc->osdev = osdev; + osdev->devc = devc; + } + + if (devc->dev_name == NULL) + devc->dev_name = "Generic USB device"; + oss_register_device (osdev, devc->dev_name); + + devc->disabled = 0; + + if (old) + { + /* Check if this interface number is already seen */ + old = 0; + for (i = 0; !old && i < devc->num_interfaces; i++) + if (devc->inum[i] == inum) + old = 1; + } + + if (!old) + { + if (devc->num_interfaces >= MAX_IFACE) + { + cmn_err (CE_CONT, "The device has too many interfaces\n"); + return NULL; + } + + devc->usbdev[devc->num_interfaces] = usbdev; + devc->last_usbdev = usbdev; + devc->inum[devc->num_interfaces] = inum; + devc->num_interfaces++; + } + else + { + devc->last_usbdev = usbdev; + } + + switch (class) + { + case USBCLASS_AUDIO: + switch (subclass) + { + case 1: /* Audiocontrol subclass */ + devc->main_osdev = osdev; + if (!usb_quiet) + cmn_err (CE_CONT, "%s audioctl device %s/%d - %s\n", + old ? "Reinsert of an" : "New", + devc->devpath, inum, devc->dev_name); + return ossusb_init_audioctl (devc, usbdev, inum, old); + break; + + case 2: /* Audio streaming subclass */ + if (!usb_quiet) + cmn_err (CE_CONT, "%s audio streaming device %s/%d - %s\n", + old ? "Reinsert of an" : "New", + devc->devpath, inum, devc->dev_name); + devc->osdev->first_mixer = devc->main_osdev->first_mixer; + return ossusb_init_audiostream (devc, usbdev, inum, old); + break; + + case 3: /* MIDI streaming subclass */ + return NULL; + break; + + default: + cmn_err (CE_CONT, + "Unknown USB audio device subclass %x:%d, device=%s\n", + class, subclass, devc->dev_name); + return NULL; + } + break; + +#if 0 + case USBCLASS_HID: + cmn_err (CE_CONT, "HID interface class %x:%d, device=%s\n", + class, subclass, devc->dev_name); + return NULL; + break; +#endif + + default: + cmn_err (CE_CONT, "Unknown USB device class %x:%d, device=%s\n", + class, subclass, devc->dev_name); + } + + return devc; +} + +static udi_usb_driver ossusb_driver = { + ossusb_device_attach, + ossusb_device_disconnect +}; + +int +oss_usb_attach (oss_device_t * osdev) +{ + if (usb_trace < 0) + { + udi_usb_trace = 0; + usb_trace = 0; + usb_quiet = 1; + } + else if (usb_trace > 0) + udi_usb_trace = usb_trace; + + return udi_attach_usbdriver (osdev, known_devices, &ossusb_driver); +} + +int +oss_usb_detach (oss_device_t * osdev) +{ + if (oss_disable_device (osdev) < 0) + return 0; + + udi_unload_usbdriver (osdev); + oss_unregister_device (osdev); + + return 1; +} |