aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/sound/usb/mixer.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/usb/mixer.c')
-rw-r--r--sound/usb/mixer.c259
1 files changed, 153 insertions, 106 deletions
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
index 81e987eaf063..409fc1164694 100644
--- a/sound/usb/mixer.c
+++ b/sound/usb/mixer.c
@@ -115,11 +115,14 @@ find_map(const struct usbmix_name_map *p, int unitid, int control)
static int
check_mapped_name(const struct usbmix_name_map *p, char *buf, int buflen)
{
+ int len;
+
if (!p || !p->name)
return 0;
buflen--;
- return strlcpy(buf, p->name, buflen);
+ len = strscpy(buf, p->name, buflen);
+ return len < 0 ? buflen : len;
}
/* ignore the error value if ignore_ctl_error flag is set */
@@ -142,6 +145,7 @@ static inline void check_mapped_dB(const struct usbmix_name_map *p,
if (p && p->dB) {
cval->dBmin = p->dB->min;
cval->dBmax = p->dB->max;
+ cval->min_mute = p->dB->min_mute;
cval->initialized = 1;
}
}
@@ -151,12 +155,15 @@ static int check_mapped_selector_name(struct mixer_build *state, int unitid,
int index, char *buf, int buflen)
{
const struct usbmix_selector_map *p;
+ int len;
if (!state->selector_map)
return 0;
for (p = state->selector_map; p->id; p++) {
- if (p->id == unitid && index < p->count)
- return strlcpy(buf, p->names[index], buflen);
+ if (p->id == unitid && index < p->count) {
+ len = strscpy(buf, p->names[index], buflen);
+ return len < 0 ? buflen : len;
+ }
}
return 0;
}
@@ -254,7 +261,7 @@ static int get_relative_value(struct usb_mixer_elem_info *cval, int val)
if (val < cval->min)
return 0;
else if (val >= cval->max)
- return (cval->max - cval->min + cval->res - 1) / cval->res;
+ return DIV_ROUND_UP(cval->max - cval->min, cval->res);
else
return (val - cval->min) / cval->res;
}
@@ -355,9 +362,8 @@ static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request,
memset(buf, 0, sizeof(buf));
- ret = snd_usb_lock_shutdown(chip) ? -EIO : 0;
- if (ret)
- goto error;
+ if (snd_usb_lock_shutdown(chip))
+ return -EIO;
idx = mixer_ctrl_intf(cval->head.mixer) | (cval->head.id << 8);
ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), bRequest,
@@ -366,8 +372,7 @@ static int get_ctl_value_v2(struct usb_mixer_elem_info *cval, int request,
snd_usb_unlock_shutdown(chip);
if (ret < 0) {
-error:
- usb_audio_err(chip,
+ usb_audio_dbg(chip,
"cannot get ctl value: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n",
request, validx, idx, cval->val_type);
return ret;
@@ -1192,15 +1197,49 @@ static void volume_control_quirks(struct usb_mixer_elem_info *cval,
cval->res = 1;
}
break;
+ case USB_ID(0x1224, 0x2a25): /* Jieli Technology USB PHY 2.0 */
+ if (!strcmp(kctl->id.name, "Mic Capture Volume")) {
+ usb_audio_info(chip,
+ "set resolution quirk: cval->res = 16\n");
+ cval->res = 16;
+ }
+ break;
+ case USB_ID(0x1bcf, 0x2283): /* NexiGo N930AF FHD Webcam */
+ if (!strcmp(kctl->id.name, "Mic Capture Volume")) {
+ usb_audio_info(chip,
+ "set resolution quirk: cval->res = 16\n");
+ cval->res = 16;
+ }
+ break;
}
}
+/* forcibly initialize the current mixer value; if GET_CUR fails, set to
+ * the minimum as default
+ */
+static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx)
+{
+ int val, err;
+
+ err = snd_usb_get_cur_mix_value(cval, ch, idx, &val);
+ if (!err)
+ return;
+ if (!cval->head.mixer->ignore_ctl_error)
+ usb_audio_warn(cval->head.mixer->chip,
+ "%d:%d: failed to get current value for ch %d (%d)\n",
+ cval->head.id, mixer_ctrl_intf(cval->head.mixer),
+ ch, err);
+ snd_usb_set_cur_mix_value(cval, ch, idx, cval->min);
+}
+
/*
* retrieve the minimum and maximum values for the specified control
*/
static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval,
int default_min, struct snd_kcontrol *kctl)
{
+ int i, idx;
+
/* for failsafe */
cval->min = default_min;
cval->max = cval->min + 1;
@@ -1213,7 +1252,6 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval,
} else {
int minchn = 0;
if (cval->cmask) {
- int i;
for (i = 0; i < MAX_CHANNELS; i++)
if (cval->cmask & (1 << i)) {
minchn = i + 1;
@@ -1232,7 +1270,7 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval,
(cval->control << 8) | minchn,
&cval->res) < 0) {
cval->res = 1;
- } else {
+ } else if (cval->head.mixer->protocol == UAC_VERSION_1) {
int last_valid_res = cval->res;
while (cval->res > 1) {
@@ -1301,6 +1339,30 @@ no_res_check:
/* totally crap, return an error */
return -EINVAL;
}
+ } else {
+ /* if the max volume is too low, it's likely a bogus range;
+ * here we use -96dB as the threshold
+ */
+ if (cval->dBmax <= -9600) {
+ usb_audio_info(cval->head.mixer->chip,
+ "%d:%d: bogus dB values (%d/%d), disabling dB reporting\n",
+ cval->head.id, mixer_ctrl_intf(cval->head.mixer),
+ cval->dBmin, cval->dBmax);
+ cval->dBmin = cval->dBmax = 0;
+ }
+ }
+
+ /* initialize all elements */
+ if (!cval->cmask) {
+ init_cur_mix_raw(cval, 0, 0);
+ } else {
+ idx = 0;
+ for (i = 0; i < MAX_CHANNELS; i++) {
+ if (cval->cmask & (1 << i)) {
+ init_cur_mix_raw(cval, i + 1, idx);
+ idx++;
+ }
+ }
}
return 0;
@@ -1338,7 +1400,7 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol,
}
uinfo->value.integer.min = 0;
uinfo->value.integer.max =
- (cval->max - cval->min + cval->res - 1) / cval->res;
+ DIV_ROUND_UP(cval->max - cval->min, cval->res);
}
return 0;
}
@@ -1429,13 +1491,11 @@ static int mixer_ctl_master_bool_get(struct snd_kcontrol *kcontrol,
return 0;
}
-/* get the connectors status and report it as boolean type */
-static int mixer_ctl_connector_get(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
+static int get_connector_value(struct usb_mixer_elem_info *cval,
+ char *name, int *val)
{
- struct usb_mixer_elem_info *cval = kcontrol->private_data;
struct snd_usb_audio *chip = cval->head.mixer->chip;
- int idx = 0, validx, ret, val;
+ int idx = 0, validx, ret;
validx = cval->control << 8 | 0;
@@ -1450,30 +1510,52 @@ static int mixer_ctl_connector_get(struct snd_kcontrol *kcontrol,
ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), UAC2_CS_CUR,
USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
validx, idx, &uac2_conn, sizeof(uac2_conn));
- val = !!uac2_conn.bNrChannels;
+ if (val)
+ *val = !!uac2_conn.bNrChannels;
} else { /* UAC_VERSION_3 */
struct uac3_insertion_ctl_blk uac3_conn;
ret = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), UAC2_CS_CUR,
USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
validx, idx, &uac3_conn, sizeof(uac3_conn));
- val = !!uac3_conn.bmConInserted;
+ if (val)
+ *val = !!uac3_conn.bmConInserted;
}
snd_usb_unlock_shutdown(chip);
if (ret < 0) {
- if (strstr(kcontrol->id.name, "Speaker")) {
- ucontrol->value.integer.value[0] = 1;
+ if (name && strstr(name, "Speaker")) {
+ if (val)
+ *val = 1;
return 0;
}
error:
usb_audio_err(chip,
"cannot get connectors status: req = %#x, wValue = %#x, wIndex = %#x, type = %d\n",
UAC_GET_CUR, validx, idx, cval->val_type);
+
+ if (val)
+ *val = 0;
+
return filter_error(cval, ret);
}
+ return ret;
+}
+
+/* get the connectors status and report it as boolean type */
+static int mixer_ctl_connector_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_info *cval = kcontrol->private_data;
+ int ret, val;
+
+ ret = get_connector_value(cval, kcontrol->id.name, &val);
+
+ if (ret < 0)
+ return ret;
+
ucontrol->value.integer.value[0] = val;
return 0;
}
@@ -1539,9 +1621,9 @@ static size_t append_ctl_name(struct snd_kcontrol *kctl, const char *str)
static void check_no_speaker_on_headset(struct snd_kcontrol *kctl,
struct snd_card *card)
{
- const char *names_to_check[] = {
+ static const char * const names_to_check[] = {
"Headset", "headset", "Headphone", "headphone", NULL};
- const char **s;
+ const char * const *s;
bool found = false;
if (strcmp("Speaker", kctl->id.name))
@@ -1556,7 +1638,7 @@ static void check_no_speaker_on_headset(struct snd_kcontrol *kctl,
if (!found)
return;
- strlcpy(kctl->id.name, "Headphone", sizeof(kctl->id.name));
+ snd_ctl_rename(card, kctl, "Headphone");
}
static const struct usb_feature_control_info *get_feature_control_info(int control)
@@ -1691,7 +1773,7 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer,
break;
default:
if (!len)
- strlcpy(kctl->id.name, audio_feature_info[control-1].name,
+ strscpy(kctl->id.name, audio_feature_info[control-1].name,
sizeof(kctl->id.name));
break;
}
@@ -1770,7 +1852,7 @@ static void get_connector_control_name(struct usb_mixer_interface *mixer,
int name_len = get_term_name(mixer->chip, term, name, name_size, 0);
if (name_len == 0)
- strlcpy(name, "Unknown", name_size);
+ strscpy(name, "Unknown", name_size);
/*
* sound/core/ctljack.c has a convention of naming jack controls
@@ -1783,6 +1865,15 @@ static void get_connector_control_name(struct usb_mixer_interface *mixer,
strlcat(name, " - Output Jack", name_size);
}
+/* get connector value to "wake up" the USB audio */
+static int connector_mixer_resume(struct usb_mixer_elem_list *list)
+{
+ struct usb_mixer_elem_info *cval = mixer_elem_list_to_info(list);
+
+ get_connector_value(cval, NULL, NULL);
+ return 0;
+}
+
/* Build a mixer control for a UAC connector control (jack-detect) */
static void build_connector_control(struct usb_mixer_interface *mixer,
const struct usbmix_name_map *imap,
@@ -1800,6 +1891,10 @@ static void build_connector_control(struct usb_mixer_interface *mixer,
if (!cval)
return;
snd_usb_mixer_elem_init_std(&cval->head, mixer, term->id);
+
+ /* set up a specific resume callback */
+ cval->head.resume = connector_mixer_resume;
+
/*
* UAC2: The first byte from reading the UAC2_TE_CONNECTOR control returns the
* number of channels connected.
@@ -1841,7 +1936,6 @@ static int parse_clock_source_unit(struct mixer_build *state, int unitid,
struct uac_clock_source_descriptor *hdr = _ftr;
struct usb_mixer_elem_info *cval;
struct snd_kcontrol *kctl;
- char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
int ret;
if (state->mixer->protocol != UAC_VERSION_2)
@@ -1878,10 +1972,9 @@ static int parse_clock_source_unit(struct mixer_build *state, int unitid,
kctl->private_free = snd_usb_mixer_elem_free;
ret = snd_usb_copy_string_desc(state->chip, hdr->iClockSource,
- name, sizeof(name));
+ kctl->id.name, sizeof(kctl->id.name));
if (ret > 0)
- snprintf(kctl->id.name, sizeof(kctl->id.name),
- "%s Validity", name);
+ append_ctl_name(kctl, " Validity");
else
snprintf(kctl->id.name, sizeof(kctl->id.name),
"Clock Source %d Validity", hdr->bClockID);
@@ -2490,7 +2583,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid,
if (check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name))) {
/* nothing */ ;
} else if (info->name) {
- strlcpy(kctl->id.name, info->name, sizeof(kctl->id.name));
+ strscpy(kctl->id.name, info->name, sizeof(kctl->id.name));
} else {
if (extension_unit)
nameid = uac_extension_unit_iExtension(desc, state->mixer->protocol);
@@ -2503,7 +2596,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid,
kctl->id.name,
sizeof(kctl->id.name));
if (!len)
- strlcpy(kctl->id.name, name, sizeof(kctl->id.name));
+ strscpy(kctl->id.name, name, sizeof(kctl->id.name));
}
append_ctl_name(kctl, " ");
append_ctl_name(kctl, valinfo->suffix);
@@ -2693,7 +2786,6 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid,
#define MAX_ITEM_NAME_LEN 64
for (i = 0; i < desc->bNrInPins; i++) {
struct usb_audio_term iterm;
- len = 0;
namelist[i] = kmalloc(MAX_ITEM_NAME_LEN, GFP_KERNEL);
if (!namelist[i]) {
err = -ENOMEM;
@@ -2743,7 +2835,7 @@ static int parse_audio_selector_unit(struct mixer_build *state, int unitid,
kctl->id.name, sizeof(kctl->id.name), 0);
/* ... or use the fixed string "USB" as the last resort */
if (!len)
- strlcpy(kctl->id.name, "USB", sizeof(kctl->id.name));
+ strscpy(kctl->id.name, "USB", sizeof(kctl->id.name));
/* and add the proper suffix */
if (desc->bDescriptorSubtype == UAC2_CLOCK_SELECTOR ||
@@ -3138,7 +3230,6 @@ static int snd_usb_mixer_controls(struct usb_mixer_interface *mixer)
state.map = map->map;
state.selector_map = map->selector_map;
mixer->connector_map = map->connector_map;
- mixer->ignore_ctl_error |= map->ignore_ctl_error;
break;
}
}
@@ -3262,8 +3353,17 @@ static void snd_usb_mixer_dump_cval(struct snd_info_buffer *buffer,
struct usb_mixer_elem_list *list)
{
struct usb_mixer_elem_info *cval = mixer_elem_list_to_info(list);
- static const char * const val_types[] = {"BOOLEAN", "INV_BOOLEAN",
- "S8", "U8", "S16", "U16"};
+ static const char * const val_types[] = {
+ [USB_MIXER_BOOLEAN] = "BOOLEAN",
+ [USB_MIXER_INV_BOOLEAN] = "INV_BOOLEAN",
+ [USB_MIXER_S8] = "S8",
+ [USB_MIXER_U8] = "U8",
+ [USB_MIXER_S16] = "S16",
+ [USB_MIXER_U16] = "U16",
+ [USB_MIXER_S32] = "S32",
+ [USB_MIXER_U32] = "U32",
+ [USB_MIXER_BESPOKEN] = "BESPOKEN",
+ };
snd_iprintf(buffer, " Info: id=%i, control=%i, cmask=0x%x, "
"channels=%i, type=\"%s\"\n", cval->head.id,
cval->control, cval->cmask, cval->channels,
@@ -3454,50 +3554,7 @@ static int snd_usb_mixer_status_create(struct usb_mixer_interface *mixer)
return 0;
}
-static int keep_iface_ctl_get(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
-{
- struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol);
-
- ucontrol->value.integer.value[0] = mixer->chip->keep_iface;
- return 0;
-}
-
-static int keep_iface_ctl_put(struct snd_kcontrol *kcontrol,
- struct snd_ctl_elem_value *ucontrol)
-{
- struct usb_mixer_interface *mixer = snd_kcontrol_chip(kcontrol);
- bool keep_iface = !!ucontrol->value.integer.value[0];
-
- if (mixer->chip->keep_iface == keep_iface)
- return 0;
- mixer->chip->keep_iface = keep_iface;
- return 1;
-}
-
-static const struct snd_kcontrol_new keep_iface_ctl = {
- .iface = SNDRV_CTL_ELEM_IFACE_CARD,
- .name = "Keep Interface",
- .info = snd_ctl_boolean_mono_info,
- .get = keep_iface_ctl_get,
- .put = keep_iface_ctl_put,
-};
-
-static int create_keep_iface_ctl(struct usb_mixer_interface *mixer)
-{
- struct snd_kcontrol *kctl = snd_ctl_new1(&keep_iface_ctl, mixer);
-
- /* need only one control per card */
- if (snd_ctl_find_id(mixer->chip->card, &kctl->id)) {
- snd_ctl_free_one(kctl);
- return 0;
- }
-
- return snd_ctl_add(mixer->chip->card, kctl);
-}
-
-int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif,
- int ignore_error)
+int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif)
{
static const struct snd_device_ops dev_ops = {
.dev_free = snd_usb_mixer_dev_free
@@ -3511,7 +3568,7 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif,
if (!mixer)
return -ENOMEM;
mixer->chip = chip;
- mixer->ignore_ctl_error = ignore_error;
+ mixer->ignore_ctl_error = !!(chip->quirk_flags & QUIRK_FLAG_IGNORE_CTL_ERROR);
mixer->id_elems = kcalloc(MAX_ID_ELEMS, sizeof(*mixer->id_elems),
GFP_KERNEL);
if (!mixer->id_elems) {
@@ -3548,10 +3605,6 @@ int snd_usb_create_mixer(struct snd_usb_audio *chip, int ctrlif,
if (err < 0)
goto _error;
- err = create_keep_iface_ctl(mixer);
- if (err < 0)
- goto _error;
-
err = snd_usb_mixer_apply_create_quirk(mixer);
if (err < 0)
goto _error;
@@ -3585,7 +3638,6 @@ void snd_usb_mixer_disconnect(struct usb_mixer_interface *mixer)
mixer->disconnected = true;
}
-#ifdef CONFIG_PM
/* stop any bus activity of a mixer */
static void snd_usb_mixer_inactivate(struct usb_mixer_interface *mixer)
{
@@ -3619,6 +3671,9 @@ static int restore_mixer_value(struct usb_mixer_elem_list *list)
struct usb_mixer_elem_info *cval = mixer_elem_list_to_info(list);
int c, err, idx;
+ if (cval->val_type == USB_MIXER_BESPOKEN)
+ return 0;
+
if (cval->cmask) {
idx = 0;
for (c = 0; c < MAX_CHANNELS; c++) {
@@ -3628,36 +3683,31 @@ static int restore_mixer_value(struct usb_mixer_elem_list *list)
err = snd_usb_set_cur_mix_value(cval, c + 1, idx,
cval->cache_val[idx]);
if (err < 0)
- return err;
+ break;
}
idx++;
}
} else {
/* master */
- if (cval->cached) {
- err = snd_usb_set_cur_mix_value(cval, 0, 0, *cval->cache_val);
- if (err < 0)
- return err;
- }
+ if (cval->cached)
+ snd_usb_set_cur_mix_value(cval, 0, 0, *cval->cache_val);
}
return 0;
}
-int snd_usb_mixer_resume(struct usb_mixer_interface *mixer, bool reset_resume)
+int snd_usb_mixer_resume(struct usb_mixer_interface *mixer)
{
struct usb_mixer_elem_list *list;
int id, err;
- if (reset_resume) {
- /* restore cached mixer values */
- for (id = 0; id < MAX_ID_ELEMS; id++) {
- for_each_mixer_elem(list, mixer, id) {
- if (list->resume) {
- err = list->resume(list);
- if (err < 0)
- return err;
- }
+ /* restore cached mixer values */
+ for (id = 0; id < MAX_ID_ELEMS; id++) {
+ for_each_mixer_elem(list, mixer, id) {
+ if (list->resume) {
+ err = list->resume(list);
+ if (err < 0)
+ return err;
}
}
}
@@ -3666,7 +3716,6 @@ int snd_usb_mixer_resume(struct usb_mixer_interface *mixer, bool reset_resume)
return snd_usb_mixer_activate(mixer);
}
-#endif
void snd_usb_mixer_elem_init_std(struct usb_mixer_elem_list *list,
struct usb_mixer_interface *mixer,
@@ -3675,7 +3724,5 @@ void snd_usb_mixer_elem_init_std(struct usb_mixer_elem_list *list,
list->mixer = mixer;
list->id = unitid;
list->dump = snd_usb_mixer_dump_cval;
-#ifdef CONFIG_PM
list->resume = restore_mixer_value;
-#endif
}