mirror of
https://github.com/alsa-project/alsa-lib.git
synced 2025-11-05 13:30:00 -05:00
When both an SPDIF and an HDMI output are present on HD-audio, both try to access IEC958 controls with index=0 although one of them must be wrong. For avoiding this conflict, the recent kernel code (3.9 and 3.8 stable) moves the IEC958 controls of an SPDIF with index=16 once when the conflict happens. In this patch, the corresponding support is added in alsa-lib side. The new "skip_rest" boolean flag is added to the hooked element definition which indicates that the rest of element array will be ignored once when this element is present and evaluated. With this new flag, the HD-audio config takes index=16 primarily, then take index=0 as fallback. Signed-off-by: Takashi Iwai <tiwai@suse.de>
649 lines
16 KiB
C
649 lines
16 KiB
C
/**
|
|
* \file control/setup.c
|
|
* \brief Routines to setup control primitives from configuration
|
|
* \author Abramo Bagnara <abramo@alsa-project.org>
|
|
* \author Jaroslav Kysela <perex@perex.cz>
|
|
* \date 2001
|
|
*
|
|
* Routines to setup control primitives from configuration
|
|
*/
|
|
/*
|
|
* Control Interface - routines for setup from configuration
|
|
* Copyright (c) 2001 by Abramo Bagnara <abramo@alsa-project.org>
|
|
* Jaroslav Kysela <perex@perex.cz>
|
|
*
|
|
*
|
|
* This library is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as
|
|
* published by the Free Software Foundation; either version 2.1 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include "local.h"
|
|
|
|
#ifndef DOC_HIDDEN
|
|
typedef struct {
|
|
unsigned int lock: 1;
|
|
unsigned int preserve: 1;
|
|
snd_ctl_elem_id_t *id;
|
|
snd_ctl_elem_info_t *info;
|
|
snd_ctl_elem_value_t *val;
|
|
snd_ctl_elem_value_t *mask;
|
|
snd_ctl_elem_value_t *old;
|
|
struct list_head list;
|
|
} snd_sctl_elem_t;
|
|
|
|
struct _snd_sctl {
|
|
int mode;
|
|
snd_ctl_t *ctl;
|
|
struct list_head elems;
|
|
};
|
|
#endif /* DOC_HIDDEN */
|
|
|
|
static int free_elems(snd_sctl_t *h)
|
|
{
|
|
int err = 0;
|
|
while (!list_empty(&h->elems)) {
|
|
snd_sctl_elem_t *elem = list_entry(h->elems.next, snd_sctl_elem_t, list);
|
|
snd_ctl_elem_id_free(elem->id);
|
|
snd_ctl_elem_info_free(elem->info);
|
|
snd_ctl_elem_value_free(elem->val);
|
|
snd_ctl_elem_value_free(elem->mask);
|
|
snd_ctl_elem_value_free(elem->old);
|
|
list_del(&elem->list);
|
|
free(elem);
|
|
}
|
|
if ((h->mode & SND_SCTL_NOFREE) == 0)
|
|
err = snd_ctl_close(h->ctl);
|
|
free(h);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* \brief Install given values to control elements
|
|
* \param h Setup control handle
|
|
* \result zero if success, otherwise a negative error code
|
|
*/
|
|
int snd_sctl_install(snd_sctl_t *h)
|
|
{
|
|
struct list_head *pos;
|
|
int err;
|
|
unsigned int k;
|
|
assert(h);
|
|
list_for_each(pos, &h->elems) {
|
|
snd_sctl_elem_t *elem = list_entry(pos, snd_sctl_elem_t, list);
|
|
unsigned int count;
|
|
snd_ctl_elem_type_t type;
|
|
if (elem->lock) {
|
|
err = snd_ctl_elem_lock(h->ctl, elem->id);
|
|
if (err < 0) {
|
|
SNDERR("Cannot lock ctl elem");
|
|
return err;
|
|
}
|
|
}
|
|
err = snd_ctl_elem_read(h->ctl, elem->old);
|
|
if (err < 0) {
|
|
SNDERR("Cannot read ctl elem");
|
|
return err;
|
|
}
|
|
count = snd_ctl_elem_info_get_count(elem->info);
|
|
type = snd_ctl_elem_info_get_type(elem->info);
|
|
switch (type) {
|
|
case SND_CTL_ELEM_TYPE_BOOLEAN:
|
|
for (k = 0; k < count; ++k) {
|
|
int old, val, mask;
|
|
old = snd_ctl_elem_value_get_boolean(elem->old, k);
|
|
mask = snd_ctl_elem_value_get_boolean(elem->mask, k);
|
|
old &= ~mask;
|
|
if (old) {
|
|
val = snd_ctl_elem_value_get_boolean(elem->val, k);
|
|
val |= old;
|
|
snd_ctl_elem_value_set_boolean(elem->val, k, val);
|
|
}
|
|
}
|
|
break;
|
|
case SND_CTL_ELEM_TYPE_INTEGER:
|
|
for (k = 0; k < count; ++k) {
|
|
long old, val, mask;
|
|
old = snd_ctl_elem_value_get_integer(elem->old, k);
|
|
mask = snd_ctl_elem_value_get_integer(elem->mask, k);
|
|
old &= ~mask;
|
|
if (old) {
|
|
val = snd_ctl_elem_value_get_integer(elem->val, k);
|
|
val |= old;
|
|
snd_ctl_elem_value_set_integer(elem->val, k, val);
|
|
}
|
|
}
|
|
break;
|
|
case SND_CTL_ELEM_TYPE_ENUMERATED:
|
|
for (k = 0; k < count; ++k) {
|
|
unsigned int old, val, mask;
|
|
old = snd_ctl_elem_value_get_enumerated(elem->old, k);
|
|
mask = snd_ctl_elem_value_get_enumerated(elem->mask, k);
|
|
old &= ~mask;
|
|
if (old) {
|
|
val = snd_ctl_elem_value_get_enumerated(elem->val, k);
|
|
val |= old;
|
|
snd_ctl_elem_value_set_enumerated(elem->val, k, val);
|
|
}
|
|
}
|
|
break;
|
|
case SND_CTL_ELEM_TYPE_IEC958:
|
|
count = sizeof(snd_aes_iec958_t);
|
|
/* Fall through */
|
|
case SND_CTL_ELEM_TYPE_BYTES:
|
|
for (k = 0; k < count; ++k) {
|
|
unsigned char old, val, mask;
|
|
old = snd_ctl_elem_value_get_byte(elem->old, k);
|
|
mask = snd_ctl_elem_value_get_byte(elem->mask, k);
|
|
old &= ~mask;
|
|
if (old) {
|
|
val = snd_ctl_elem_value_get_byte(elem->val, k);
|
|
val |= old;
|
|
snd_ctl_elem_value_set_byte(elem->val, k, val);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
err = snd_ctl_elem_write(h->ctl, elem->val);
|
|
if (err < 0) {
|
|
SNDERR("Cannot write ctl elem");
|
|
return err;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Remove (restore) previous values from control elements
|
|
* \param h Setup control handle
|
|
* \result zero if success, otherwise a negative error code
|
|
*/
|
|
int snd_sctl_remove(snd_sctl_t *h)
|
|
{
|
|
struct list_head *pos;
|
|
int err;
|
|
assert(h);
|
|
list_for_each(pos, &h->elems) {
|
|
snd_sctl_elem_t *elem = list_entry(pos, snd_sctl_elem_t, list);
|
|
if (elem->lock) {
|
|
err = snd_ctl_elem_unlock(h->ctl, elem->id);
|
|
if (err < 0) {
|
|
SNDERR("Cannot unlock ctl elem");
|
|
return err;
|
|
}
|
|
}
|
|
/* Only restore the old value if it differs from the requested
|
|
* value, because if it has changed restoring the old value
|
|
* overrides the change. Take for example, a voice modem with
|
|
* a .conf that sets preserve off-hook. Start playback (on-hook
|
|
* to off-hook), start record (off-hook to off-hook), stop
|
|
* playback (off-hook to restore on-hook), stop record (on-hook
|
|
* to restore off-hook), Clearly you don't want to leave the
|
|
* modem "on the phone" now that there isn't any playback or
|
|
* recording active.
|
|
*/
|
|
if (elem->preserve && snd_ctl_elem_value_compare(elem->val, elem->old)) {
|
|
err = snd_ctl_elem_write(h->ctl, elem->old);
|
|
if (err < 0) {
|
|
SNDERR("Cannot restore ctl elem");
|
|
return err;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int snd_config_get_ctl_elem_enumerated(snd_config_t *n, snd_ctl_t *ctl,
|
|
snd_ctl_elem_info_t *info)
|
|
{
|
|
const char *str;
|
|
long val;
|
|
unsigned int idx, items;
|
|
switch (snd_config_get_type(n)) {
|
|
case SND_CONFIG_TYPE_INTEGER:
|
|
snd_config_get_integer(n, &val);
|
|
return val;
|
|
case SND_CONFIG_TYPE_STRING:
|
|
snd_config_get_string(n, &str);
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
items = snd_ctl_elem_info_get_items(info);
|
|
for (idx = 0; idx < items; idx++) {
|
|
int err;
|
|
snd_ctl_elem_info_set_item(info, idx);
|
|
err = snd_ctl_elem_info(ctl, info);
|
|
if (err < 0) {
|
|
SNDERR("Cannot obtain info for CTL elem");
|
|
return err;
|
|
}
|
|
if (strcmp(str, snd_ctl_elem_info_get_item_name(info)) == 0)
|
|
return idx;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int snd_config_get_ctl_elem_value(snd_config_t *conf,
|
|
snd_ctl_t *ctl,
|
|
snd_ctl_elem_value_t *val,
|
|
snd_ctl_elem_value_t *mask,
|
|
snd_ctl_elem_info_t *info)
|
|
{
|
|
int err;
|
|
snd_config_iterator_t i, next;
|
|
snd_ctl_elem_id_t *id;
|
|
snd_ctl_elem_type_t type;
|
|
unsigned int count;
|
|
long v;
|
|
long idx;
|
|
snd_ctl_elem_id_alloca(&id);
|
|
snd_ctl_elem_value_get_id(val, id);
|
|
count = snd_ctl_elem_info_get_count(info);
|
|
type = snd_ctl_elem_info_get_type(info);
|
|
if (count == 1) {
|
|
switch (type) {
|
|
case SND_CTL_ELEM_TYPE_BOOLEAN:
|
|
v = snd_config_get_bool(conf);
|
|
if (v >= 0) {
|
|
snd_ctl_elem_value_set_boolean(val, 0, v);
|
|
if (mask)
|
|
snd_ctl_elem_value_set_boolean(mask, 0, 1);
|
|
return 0;
|
|
}
|
|
break;
|
|
case SND_CTL_ELEM_TYPE_INTEGER:
|
|
err = snd_config_get_integer(conf, &v);
|
|
if (err == 0) {
|
|
snd_ctl_elem_value_set_integer(val, 0, v);
|
|
if (mask)
|
|
snd_ctl_elem_value_set_integer(mask, 0, ~0L);
|
|
return 0;
|
|
}
|
|
break;
|
|
case SND_CTL_ELEM_TYPE_ENUMERATED:
|
|
v = snd_config_get_ctl_elem_enumerated(conf, ctl, info);
|
|
if (v >= 0) {
|
|
snd_ctl_elem_value_set_enumerated(val, 0, v);
|
|
if (mask)
|
|
snd_ctl_elem_value_set_enumerated(mask, 0, ~0);
|
|
return 0;
|
|
}
|
|
break;
|
|
case SND_CTL_ELEM_TYPE_BYTES:
|
|
case SND_CTL_ELEM_TYPE_IEC958:
|
|
break;
|
|
default:
|
|
SNDERR("Unknown control type: %d", type);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
switch (type) {
|
|
case SND_CTL_ELEM_TYPE_IEC958:
|
|
count = sizeof(snd_aes_iec958_t);
|
|
/* Fall through */
|
|
case SND_CTL_ELEM_TYPE_BYTES:
|
|
{
|
|
const char *buf;
|
|
err = snd_config_get_string(conf, &buf);
|
|
if (err >= 0) {
|
|
int c1 = 0;
|
|
unsigned int len = strlen(buf);
|
|
unsigned int idx = 0;
|
|
if (len % 2 != 0 || len > count * 2) {
|
|
_bad_content:
|
|
SNDERR("bad value content\n");
|
|
return -EINVAL;
|
|
}
|
|
while (*buf) {
|
|
int c = *buf++;
|
|
if (c >= '0' && c <= '9')
|
|
c -= '0';
|
|
else if (c >= 'a' && c <= 'f')
|
|
c = c - 'a' + 10;
|
|
else if (c >= 'A' && c <= 'F')
|
|
c = c - 'A' + 10;
|
|
else {
|
|
goto _bad_content;
|
|
}
|
|
if (idx % 2 == 1) {
|
|
snd_ctl_elem_value_set_byte(val, idx / 2, c1 << 4 | c);
|
|
if (mask)
|
|
snd_ctl_elem_value_set_byte(mask, idx / 2, 0xff);
|
|
} else
|
|
c1 = c;
|
|
idx++;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
if (snd_config_get_type(conf) != SND_CONFIG_TYPE_COMPOUND) {
|
|
SNDERR("bad value type");
|
|
return -EINVAL;
|
|
}
|
|
|
|
snd_config_for_each(i, next, conf) {
|
|
snd_config_t *n = snd_config_iterator_entry(i);
|
|
const char *id;
|
|
if (snd_config_get_id(n, &id) < 0)
|
|
continue;
|
|
err = safe_strtol(id, &idx);
|
|
if (err < 0 || idx < 0 || (unsigned int) idx >= count) {
|
|
SNDERR("bad value index");
|
|
return -EINVAL;
|
|
}
|
|
switch (type) {
|
|
case SND_CTL_ELEM_TYPE_BOOLEAN:
|
|
v = snd_config_get_bool(n);
|
|
if (v < 0)
|
|
goto _bad_content;
|
|
snd_ctl_elem_value_set_boolean(val, idx, v);
|
|
if (mask)
|
|
snd_ctl_elem_value_set_boolean(mask, idx, 1);
|
|
break;
|
|
case SND_CTL_ELEM_TYPE_INTEGER:
|
|
err = snd_config_get_integer(n, &v);
|
|
if (err < 0)
|
|
goto _bad_content;
|
|
snd_ctl_elem_value_set_integer(val, idx, v);
|
|
if (mask)
|
|
snd_ctl_elem_value_set_integer(mask, idx, ~0L);
|
|
break;
|
|
case SND_CTL_ELEM_TYPE_ENUMERATED:
|
|
v = snd_config_get_ctl_elem_enumerated(n, ctl, info);
|
|
if (v < 0)
|
|
goto _bad_content;
|
|
snd_ctl_elem_value_set_enumerated(val, idx, v);
|
|
if (mask)
|
|
snd_ctl_elem_value_set_enumerated(mask, idx, ~0);
|
|
break;
|
|
case SND_CTL_ELEM_TYPE_BYTES:
|
|
case SND_CTL_ELEM_TYPE_IEC958:
|
|
err = snd_config_get_integer(n, &v);
|
|
if (err < 0 || v < 0 || v > 255)
|
|
goto _bad_content;
|
|
snd_ctl_elem_value_set_byte(val, idx, v);
|
|
if (mask)
|
|
snd_ctl_elem_value_set_byte(mask, idx, 0xff);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int add_elem(snd_sctl_t *h, snd_config_t *_conf, snd_config_t *private_data, int *quit)
|
|
{
|
|
snd_config_t *conf;
|
|
snd_config_iterator_t i, next;
|
|
int iface = SND_CTL_ELEM_IFACE_MIXER;
|
|
const char *name = NULL;
|
|
long index = 0;
|
|
long device = -1;
|
|
long subdevice = -1;
|
|
int lock = 0;
|
|
int preserve = 0;
|
|
int optional = 0;
|
|
int skip_rest = 0;
|
|
snd_config_t *value = NULL, *mask = NULL;
|
|
snd_sctl_elem_t *elem = NULL;
|
|
int err;
|
|
err = snd_config_expand(_conf, _conf, NULL, private_data, &conf);
|
|
if (err < 0)
|
|
return err;
|
|
snd_config_for_each(i, next, conf) {
|
|
snd_config_t *n = snd_config_iterator_entry(i);
|
|
const char *id;
|
|
if (snd_config_get_id(n, &id) < 0)
|
|
continue;
|
|
if (strcmp(id, "comment") == 0)
|
|
continue;
|
|
if (strcmp(id, "iface") == 0 || strcmp(id, "interface") == 0) {
|
|
const char *ptr;
|
|
if ((err = snd_config_get_string(n, &ptr)) < 0) {
|
|
SNDERR("field %s is not a string", id);
|
|
goto _err;
|
|
}
|
|
if ((err = snd_config_get_ctl_iface_ascii(ptr)) < 0) {
|
|
SNDERR("Invalid value for '%s'", id);
|
|
goto _err;
|
|
}
|
|
iface = err;
|
|
continue;
|
|
}
|
|
if (strcmp(id, "name") == 0) {
|
|
if ((err = snd_config_get_string(n, &name)) < 0) {
|
|
SNDERR("field %s is not a string", id);
|
|
goto _err;
|
|
}
|
|
continue;
|
|
}
|
|
if (strcmp(id, "index") == 0) {
|
|
if ((err = snd_config_get_integer(n, &index)) < 0) {
|
|
SNDERR("field %s is not an integer", id);
|
|
goto _err;
|
|
}
|
|
continue;
|
|
}
|
|
if (strcmp(id, "device") == 0) {
|
|
if ((err = snd_config_get_integer(n, &device)) < 0) {
|
|
SNDERR("field %s is not an integer", id);
|
|
goto _err;
|
|
}
|
|
continue;
|
|
}
|
|
if (strcmp(id, "subdevice") == 0) {
|
|
if ((err = snd_config_get_integer(n, &subdevice)) < 0) {
|
|
SNDERR("field %s is not an integer", id);
|
|
goto _err;
|
|
}
|
|
continue;
|
|
}
|
|
if (strcmp(id, "lock") == 0) {
|
|
err = snd_config_get_bool(n);
|
|
if (err < 0)
|
|
goto _err;
|
|
lock = err;
|
|
continue;
|
|
}
|
|
if (strcmp(id, "preserve") == 0) {
|
|
err = snd_config_get_bool(n);
|
|
if (err < 0)
|
|
goto _err;
|
|
preserve = err;
|
|
continue;
|
|
}
|
|
if (strcmp(id, "value") == 0) {
|
|
value = n;
|
|
continue;
|
|
}
|
|
if (strcmp(id, "mask") == 0) {
|
|
mask = n;
|
|
continue;
|
|
}
|
|
if (strcmp(id, "optional") == 0) {
|
|
err = snd_config_get_bool(n);
|
|
if (err < 0)
|
|
goto _err;
|
|
optional = err;
|
|
continue;
|
|
}
|
|
if (strcmp(id, "skip_rest") == 0) {
|
|
err = snd_config_get_bool(n);
|
|
if (err < 0)
|
|
goto _err;
|
|
skip_rest = err;
|
|
continue;
|
|
}
|
|
SNDERR("Unknown field %s", id);
|
|
return -EINVAL;
|
|
}
|
|
if (name == NULL) {
|
|
SNDERR("Missing control name");
|
|
err = -EINVAL;
|
|
goto _err;
|
|
}
|
|
if (value == NULL) {
|
|
SNDERR("Missing control value");
|
|
err = -EINVAL;
|
|
goto _err;
|
|
}
|
|
if (device < 0)
|
|
device = 0;
|
|
if (subdevice < 0)
|
|
subdevice = 0;
|
|
elem = calloc(1, sizeof(*elem));
|
|
if (!elem)
|
|
return -ENOMEM;
|
|
err = snd_ctl_elem_id_malloc(&elem->id);
|
|
if (err < 0)
|
|
goto _err;
|
|
err = snd_ctl_elem_info_malloc(&elem->info);
|
|
if (err < 0)
|
|
goto _err;
|
|
err = snd_ctl_elem_value_malloc(&elem->val);
|
|
if (err < 0)
|
|
goto _err;
|
|
err = snd_ctl_elem_value_malloc(&elem->mask);
|
|
if (err < 0)
|
|
goto _err;
|
|
err = snd_ctl_elem_value_malloc(&elem->old);
|
|
if (err < 0)
|
|
goto _err;
|
|
elem->lock = lock;
|
|
elem->preserve = preserve;
|
|
snd_ctl_elem_id_set_interface(elem->id, iface);
|
|
snd_ctl_elem_id_set_name(elem->id, name);
|
|
snd_ctl_elem_id_set_index(elem->id, index);
|
|
snd_ctl_elem_id_set_device(elem->id, device);
|
|
snd_ctl_elem_id_set_subdevice(elem->id, subdevice);
|
|
snd_ctl_elem_info_set_id(elem->info, elem->id);
|
|
err = snd_ctl_elem_info(h->ctl, elem->info);
|
|
if (err < 0) {
|
|
if (! optional)
|
|
SNDERR("Cannot obtain info for CTL elem (%s,'%s',%li,%li,%li): %s", snd_ctl_elem_iface_name(iface), name, index, device, subdevice, snd_strerror(err));
|
|
goto _err;
|
|
} else {
|
|
if (skip_rest)
|
|
*quit = 1;
|
|
}
|
|
snd_ctl_elem_value_set_id(elem->val, elem->id);
|
|
snd_ctl_elem_value_set_id(elem->old, elem->id);
|
|
if (mask) {
|
|
err = snd_config_get_ctl_elem_value(value, h->ctl, elem->val, NULL, elem->info);
|
|
if (err < 0)
|
|
goto _err;
|
|
err = snd_config_get_ctl_elem_value(mask, h->ctl, elem->mask, NULL, elem->info);
|
|
if (err < 0)
|
|
goto _err;
|
|
} else {
|
|
err = snd_config_get_ctl_elem_value(value, h->ctl, elem->val, elem->mask, elem->info);
|
|
if (err < 0)
|
|
goto _err;
|
|
}
|
|
|
|
err = snd_config_get_ctl_elem_value(value, h->ctl, elem->val, elem->mask, elem->info);
|
|
if (err < 0)
|
|
goto _err;
|
|
list_add_tail(&elem->list, &h->elems);
|
|
|
|
_err:
|
|
if (err < 0 && elem) {
|
|
if (elem->id)
|
|
snd_ctl_elem_id_free(elem->id);
|
|
if (elem->info)
|
|
snd_ctl_elem_info_free(elem->info);
|
|
if (elem->val)
|
|
snd_ctl_elem_value_free(elem->val);
|
|
if (elem->mask)
|
|
snd_ctl_elem_value_free(elem->mask);
|
|
if (elem->old)
|
|
snd_ctl_elem_value_free(elem->old);
|
|
free(elem);
|
|
if (err != -ENOMEM && optional)
|
|
err = 0; /* ignore the error */
|
|
}
|
|
if (conf)
|
|
snd_config_delete(conf);
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* \brief Build setup control handle
|
|
* \param sctl Result - setup control handle
|
|
* \param handle Master control handle
|
|
* \param conf Setup configuration
|
|
* \param private_data Private data for runtime evaluation
|
|
* \param mode Build mode - SND_SCTL_xxxx
|
|
* \result zero if success, otherwise a negative error code
|
|
*/
|
|
int snd_sctl_build(snd_sctl_t **sctl, snd_ctl_t *handle, snd_config_t *conf, snd_config_t *private_data, int mode)
|
|
{
|
|
snd_sctl_t *h;
|
|
snd_config_iterator_t i, next;
|
|
int err, quit = 0;
|
|
|
|
assert(sctl);
|
|
assert(handle);
|
|
assert(conf);
|
|
*sctl = NULL;
|
|
if (snd_config_get_type(conf) != SND_CONFIG_TYPE_COMPOUND)
|
|
return -EINVAL;
|
|
h = calloc(1, sizeof(*h));
|
|
if (!h) {
|
|
if (mode & SND_SCTL_NOFREE)
|
|
return -ENOMEM;
|
|
snd_ctl_close(handle);
|
|
return -ENOMEM;
|
|
}
|
|
h->mode = mode;
|
|
h->ctl = handle;
|
|
INIT_LIST_HEAD(&h->elems);
|
|
snd_config_for_each(i, next, conf) {
|
|
snd_config_t *n = snd_config_iterator_entry(i);
|
|
err = add_elem(h, n, private_data, &quit);
|
|
if (err < 0) {
|
|
free_elems(h);
|
|
return err;
|
|
}
|
|
if (quit)
|
|
break;
|
|
}
|
|
*sctl = h;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief Free setup control handle
|
|
* \param sctl Setup control handle
|
|
* \result zero if success, otherwise a negative error code
|
|
*/
|
|
int snd_sctl_free(snd_sctl_t *sctl)
|
|
{
|
|
assert(sctl);
|
|
return free_elems(sctl);
|
|
}
|