summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLiam Girdwood <lrg@slimlogic.co.uk>2010-08-23 18:17:18 +0200
committerJaroslav Kysela <perex@perex.cz>2010-08-23 20:05:35 +0200
commit7699c92bd4bcd65098af631f6f5e2e7e9235caeb (patch)
tree6aa9608971cb1a5977e544457e89f733cffd0a76
parent4adb3c325bccf0dec9c37b7462832d17033caf93 (diff)
downloadalsa-lib-7699c92bd4bcd65098af631f6f5e2e7e9235caeb.tar.gz
ucm: core - Add initial Use Case Manager support.
This patch adds audio Use Case management support to alsa-lib. UCM is designed to abstract the audio device hardware mixer controls into high level abstract use cases. The use case manager works by configuring the sound card ALSA kcontrols to change the hardware digital and analog audio routing to match the requested device use case. The use case manager kcontrol configurations are stored in easy to modify text files. UCM development has been kindly sponsored by Texas Instruments Inc, Wolfson Microelectronics PLC and Slimlogic Ltd. CC: Mark Brown <broonie@opensource.wolfsonmicro.com> Signed-off-by: Stefan Schmidt <stefan@slimlogic.co.uk> Signed-off-by: Justin Xu <justinx@slimlogic.co.uk> Signed-off-by: Liam Girdwood <lrg@slimlogic.co.uk>
-rw-r--r--src/use-case.c3515
1 files changed, 3515 insertions, 0 deletions
diff --git a/src/use-case.c b/src/use-case.c
new file mode 100644
index 00000000..b45fe1f1
--- /dev/null
+++ b/src/use-case.c
@@ -0,0 +1,3515 @@
+/*
+ * 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 of the License, or (at your option) any later version.
+ *
+ * This library 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 General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Support for the verb/device/modifier core logic and API,
+ * command line tool and file parser was kindly sponsored by
+ * Texas Instruments Inc.
+ * Support for multiple active modifiers and devices,
+ * transition sequences, multiple client access and user defined use
+ * cases was kindly sponsored by Wolfson Microelectronics PLC.
+ *
+ * Copyright (C) 2008-2010 SlimLogic Ltd
+ * Copyright (C) 2010 Wolfson Microelectronics PLC
+ * Copyright (C) 2010 Texas Instruments Inc.
+ * Authors: Liam Girdwood <lrg@slimlogic.co.uk>
+ * Stefan Schmidt <stefan@slimlogic.co.uk>
+ * Justin Xu <justinx@slimlogic.co.uk>
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <alsa/asoundlib.h>
+#include <pthread.h>
+
+#include "../include/use-case.h"
+#include "../include/iatomic.h"
+
+#define PRE_SEQ 0
+#define POST_SEQ 1
+#define MAX_VERB 32
+#define MAX_DEVICE 64
+#define MAX_MODIFIER 64
+#define MAX_NAME 64
+#define MAX_FILE 256
+#define MAX_BUF 256
+#define ALSA_USE_CASE_DIR "/usr/share/alsa/ucm"
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+#define VERB_NOT_INITIALISED -1
+
+/*
+ * Stores all use case settings for 1 kcontrol. Hence we have a
+ * control_settings for each kcontrol in card.
+ */
+struct control_settings {
+ char name[MAX_NAME];
+ unsigned int id;
+ snd_ctl_elem_type_t type;
+ short count; /* 1 = mono, 2 = stereo, etc */
+ unsigned short *value;
+};
+
+/*
+ * If sleep is 0 the element contains the settings in control. Else sleep
+ * contains the sleep time in micro seconds.
+ */
+struct sequence_element {
+ unsigned int sleep; /* Sleep time in msecs if sleep element, else 0 */
+ struct control_settings *control;
+ struct sequence_element *next; /* Pointer to next list element */
+};
+
+/*
+ * Transition sequences. i.e. transition between one verb, device, mod to another
+ */
+struct transition_sequence {
+ char *name;
+ struct sequence_element *transition;
+ struct transition_sequence *next;
+};
+
+/*
+ * Modifier Supported Devicees.
+ */
+struct dev_list {
+ char *name;
+ struct dev_list *next;
+};
+
+
+/*
+ * Describes a Use Case Modifier and it's enable and disable sequences.
+ * A use case verb can have N modifiers.
+ */
+struct use_case_modifier {
+ char *name;
+ char *comment;
+
+ /* modifier enable and disable sequences */
+ struct sequence_element *enable;
+ struct sequence_element *disable;
+
+ /* modifier transition list */
+ struct transition_sequence *transition_list;
+
+ /* list of supported devices per modifier */
+ struct dev_list *dev_list;
+
+ /* ALSA PCM devices associated with any modifier PCM streams */
+ int capture_pcm;
+ int playback_pcm;
+
+ /* Any modifier stream QoS */
+ enum snd_use_case_qos qos;
+
+ /* aliased controls */
+ char *playback_volume_id;
+ char *playback_switch_id;
+ char *capture_volume_id;
+ char *capture_switch_id;
+};
+
+/*
+ * Describes a Use Case Device and it's enable and disable sequences.
+ * A use case verb can have N devices.
+ */
+struct use_case_device {
+ char *name;
+ char *comment;
+ int idx; /* index for similar devices i.e. 2 headphone jacks */
+
+ /* device enable and disable sequences */
+ struct sequence_element *enable;
+ struct sequence_element *disable;
+
+ /* device transition list */
+ struct transition_sequence *transition_list;
+
+ /* aliased controls */
+ char *playback_volume_id;
+ char *playback_switch_id;
+ char *capture_volume_id;
+ char *capture_switch_id;
+};
+
+/*
+ * Describes a Use Case Verb and it's enable and disable sequences.
+ * A use case verb can have N devices and N modifiers.
+ */
+struct use_case_verb {
+ char *name;
+ char *comment;
+
+ /* verb enable and disable sequences */
+ struct sequence_element *enable;
+ struct sequence_element *disable;
+
+ /* verb transition list */
+ struct transition_sequence *transition_list;
+
+ /* verb PCMs and QoS */
+ enum snd_use_case_qos qos;
+ int capture_pcm;
+ int playback_pcm;
+
+ /* hardware devices that can be used with this use case */
+ int num_devices;
+ struct use_case_device *device;
+ /*
+ * device_list[i] shares string with device[i].name,
+ * so device_list don't need not be freed
+ */
+ const char *device_list[MAX_DEVICE];
+
+ /* modifiers that can be used with this use case */
+ int num_modifiers;
+ struct use_case_modifier *modifier;
+ /*
+ * modifier_list[i] shares string with modifier[i].name,
+ * so modifier_list don't need not be freed
+ */
+ const char *modifier_list[MAX_MODIFIER];
+};
+
+struct ucm_card {
+ int current_verb;
+ int current_device[MAX_DEVICE];
+ int current_modifier[MAX_MODIFIER];
+};
+
+/*
+ * Manages a sound card and all its use cases.
+ */
+struct snd_use_case_mgr {
+ pthread_mutex_t mutex;
+ char *card_name;
+ char *ctl_name;
+ struct ucm_card card;
+
+ /* use case verb, devices and modifier configs parsed from files */
+ int num_verbs; /* number of supported use case verbs */
+ struct use_case_verb *verb; /* var len array of use case info */
+ /*
+ * verb_list[i] shares string with verb[i].name,
+ * so verb_list don't need not be freed
+ */
+ const char *verb_list[MAX_VERB];
+
+ /* sound card ALSA kcontrol read from sound card device */
+ struct control_settings *control; /* var len array of controls */
+
+ /* snd ctl */
+ int count;
+ snd_ctl_t *handle;
+ snd_ctl_card_info_t *info;
+ snd_ctl_elem_list_t *list;
+ snd_ctl_elem_id_t *id;
+};
+
+static void uc_mgr_error(const char *fmt,...)
+{
+ va_list va;
+ va_start(va, fmt);
+ fprintf(stderr, "ucm: ");
+ vfprintf(stderr, fmt, va);
+ va_end(va);
+}
+
+#define uc_error(fmt, arg...) do { \
+ uc_mgr_error("%s() - " fmt "\n", __FUNCTION__ , ## arg); \
+} while (0)
+
+static void uc_mgr_stdout(const char *fmt,...)
+{
+ va_list va;
+ va_start(va, fmt);
+ vfprintf(stdout, fmt, va);
+ va_end(va);
+}
+
+#undef UC_MGR_DEBUG
+
+#ifdef UC_MGR_DEBUG
+#define uc_dbg(fmt, arg...) do { \
+ uc_mgr_stdout("%s() - " fmt "\n", __FUNCTION__ , ## arg); \
+} while (0)
+#else
+#define uc_dbg(fmt, arg...)
+#endif
+
+static int set_control(snd_ctl_t *handle, snd_ctl_elem_id_t *id,
+ snd_use_case_mgr_t *uc_mgr, unsigned short value[]);
+
+static inline void set_value(struct control_settings *control,
+ int count, unsigned short val)
+{
+ uc_dbg("value %d, count %d", val, count);
+ control->value[count] = val;
+}
+
+static inline unsigned short get_value(struct control_settings *control,
+ int count)
+{
+ return control->value[count];
+}
+
+static inline void set_device_status(struct snd_use_case_mgr *uc_mgr,
+ int device_id, int status)
+{
+ struct use_case_verb *verb;
+
+ verb = &uc_mgr->verb[uc_mgr->card.current_verb];
+ uc_mgr->card.current_device[device_id] = status;
+}
+
+static inline void set_modifier_status(struct snd_use_case_mgr *uc_mgr,
+ int modifier_id, int status)
+{
+ struct use_case_verb *verb;
+
+ verb = &uc_mgr->verb[uc_mgr->card.current_verb];
+ uc_mgr->card.current_modifier[modifier_id] = status;
+}
+
+static inline int get_device_status(struct snd_use_case_mgr *uc_mgr,
+ int device_id)
+{
+ struct use_case_verb *verb;
+
+ verb = &uc_mgr->verb[uc_mgr->card.current_verb];
+ return uc_mgr->card.current_device[device_id];
+}
+
+static inline int get_modifier_status(struct snd_use_case_mgr *uc_mgr,
+ int modifier_id)
+{
+ struct use_case_verb *verb;
+
+ verb = &uc_mgr->verb[uc_mgr->card.current_verb];
+ return uc_mgr->card.current_modifier[modifier_id];
+}
+
+static int dump_control(snd_ctl_t *handle, snd_ctl_elem_id_t *id)
+{
+ int err, count, i;
+ snd_ctl_elem_info_t *info;
+ snd_ctl_elem_type_t type;
+ snd_ctl_elem_value_t *control;
+
+ snd_ctl_elem_info_alloca(&info);
+ snd_ctl_elem_value_alloca(&control);
+
+ snd_ctl_elem_info_set_id(info, id);
+ err = snd_ctl_elem_info(handle, info);
+ if (err < 0) {
+ uc_error("error: failed to get ctl info: %s\n",
+ snd_strerror(err));
+ return err;
+ }
+
+ snd_ctl_elem_value_set_id(control, id);
+ snd_ctl_elem_read(handle, control);
+
+ type = snd_ctl_elem_info_get_type(info);
+ count = snd_ctl_elem_info_get_count(info);
+ if (count == 0)
+ return 0;
+
+ uc_mgr_stdout("'%s':%d:",
+ snd_ctl_elem_id_get_name(id), count);
+
+ switch (type) {
+ case SND_CTL_ELEM_TYPE_BOOLEAN:
+ for (i = 0; i < count - 1; i++)
+ uc_mgr_stdout("%d,",
+ snd_ctl_elem_value_get_boolean(control, i));
+ uc_mgr_stdout("%d",
+ snd_ctl_elem_value_get_boolean(control, i));
+ break;
+ case SND_CTL_ELEM_TYPE_INTEGER:
+ for (i = 0; i < count - 1; i++)
+ uc_mgr_stdout("%d,",
+ snd_ctl_elem_value_get_integer(control, i));
+ uc_mgr_stdout("%d",
+ snd_ctl_elem_value_get_integer(control, i));
+ break;
+ case SND_CTL_ELEM_TYPE_INTEGER64:
+ for (i = 0; i < count - 1; i++)
+ uc_mgr_stdout("%ld,",
+ snd_ctl_elem_value_get_integer64(control, i));
+ uc_mgr_stdout("%ld",
+ snd_ctl_elem_value_get_integer64(control, i));
+ break;
+ case SND_CTL_ELEM_TYPE_ENUMERATED:
+ for (i = 0; i < count - 1; i++)
+ uc_mgr_stdout("%d,",
+ snd_ctl_elem_value_get_enumerated(control, i));
+ uc_mgr_stdout("%d",
+ snd_ctl_elem_value_get_enumerated(control, i));
+ break;
+ case SND_CTL_ELEM_TYPE_BYTES:
+ for (i = 0; i < count - 1; i++)
+ uc_mgr_stdout("%2.2x,",
+ snd_ctl_elem_value_get_byte(control, i));
+ uc_mgr_stdout("%2.2x",
+ snd_ctl_elem_value_get_byte(control, i));
+ break;
+ default:
+ break;
+ }
+ uc_mgr_stdout("\n");
+ return 0;
+}
+
+/*
+ * Add new kcontrol from sound card into memory database.
+ */
+static int add_control(snd_ctl_t *handle, snd_ctl_elem_id_t *id,
+ struct control_settings *control_settings)
+{
+ int err;
+ snd_ctl_elem_info_t *info;
+ snd_ctl_elem_value_t *control;
+
+ snd_ctl_elem_info_alloca(&info);
+ snd_ctl_elem_value_alloca(&control);
+
+ snd_ctl_elem_info_set_id(info, id);
+ err = snd_ctl_elem_info(handle, info);
+ if (err < 0) {
+ uc_error("error: failed to get ctl info: %s\n",
+ snd_strerror(err));
+ return err;
+ }
+
+ snd_ctl_elem_value_set_id(control, id);
+ snd_ctl_elem_read(handle, control);
+
+ strncpy(control_settings->name, snd_ctl_elem_id_get_name(id),
+ MAX_NAME);
+
+ control_settings->name[MAX_NAME - 1] = 0;
+ control_settings->count = snd_ctl_elem_info_get_count(info);
+ control_settings->type = snd_ctl_elem_info_get_type(info);
+ control_settings->id = snd_ctl_elem_id_get_numid(id);
+ uc_dbg("control name %s", control_settings->name);
+ uc_dbg("control count %d", control_settings->count);
+ uc_dbg("control type %d", control_settings->type);
+ uc_dbg("control id %d", control_settings->id);
+ return 0;
+}
+
+static int set_control_default(snd_use_case_mgr_t *uc_mgr,
+ struct control_settings *control)
+{
+ snd_ctl_elem_id_t *id;
+ int i, ret = -ENODEV;
+ unsigned int numid;
+
+ snd_ctl_elem_id_alloca(&id);
+
+ /* Where is id lookup from numid if you need it? */
+ for (i = 0; i < uc_mgr->count; ++i) {
+ snd_ctl_elem_list_get_id(uc_mgr->list, i, id);
+ numid = snd_ctl_elem_id_get_numid(id);
+ if (numid == control->id) {
+ ret = set_control(uc_mgr->handle, id, uc_mgr,
+ control->value);
+ goto out;
+ }
+ }
+ uc_error("error: could not find control ID %s : %d",
+ control->name, control->id);
+out:
+ return ret;
+}
+
+static int get_control_id(snd_use_case_mgr_t *uc_mgr,
+ struct control_settings *control)
+{
+ int i = 0;
+ struct control_settings *card_control;
+
+ uc_dbg("name %s count %d",
+ control->name, control->count);
+
+ for (i = 0; i < uc_mgr->count; i++) {
+ card_control = &uc_mgr->control[i];
+ if (!strcmp(card_control->name, control->name)) {
+ control->id = uc_mgr->control[i].id;
+ uc_dbg("Get id %d", control->id);
+ return 0;
+ }
+ }
+
+ uc_error("error: control name %s is not available", control->name);
+
+ return -EINVAL;
+}
+
+static char *get_control_name(char *buf, int line, char *file)
+{
+ char name[MAX_NAME];
+ char *name_start, *name_end, *tbuf = buf;
+
+ /* get name start */
+ while (*tbuf != 0 && *tbuf != '\'')
+ tbuf++;
+ if (*tbuf == 0)
+ return NULL;
+ name_start = ++tbuf;
+
+ /* get name end */
+ while (*tbuf != 0 && *tbuf != '\'')
+ tbuf++;
+ if (*tbuf == 0)
+ return NULL;
+ name_end = tbuf++;
+
+ /* copy name */
+ if ((name_end - name_start) > MAX_NAME) {
+ uc_error("error: %s:%d name too big at %d chars",
+ file, line, name_end - name_start);
+ return NULL;
+ }
+ strncpy(name, name_start, name_end - name_start);
+ name[name_end - name_start] = 0;
+ return strdup(name);
+}
+
+/*
+ * Parse a single control from file.
+ *
+ * Controls are in the following form:-
+ *
+ * 'name':channels:value0,value1,...,valueN
+ *
+ * e.g.
+ * 'Master Playback Switch':2:0,0
+ */
+static int parse_control(snd_use_case_mgr_t *uc_mgr,
+ struct control_settings *control, char *buf,
+ int line, char *file)
+{
+ char name[MAX_NAME];
+ int count, i;
+ char *name_start, *name_end, *tbuf;
+
+ uc_dbg("%s", buf);
+
+ tbuf = buf;
+
+ /* get name start */
+ while (*tbuf != 0 && *tbuf != '\'')
+ tbuf++;
+ if (*tbuf == 0)
+ return -EINVAL;
+ name_start = ++tbuf;
+
+ /* get name end */
+ while (*tbuf != 0 && *tbuf != '\'')
+ tbuf++;
+ if (*tbuf == 0)
+ return -EINVAL;
+ name_end = tbuf++;
+
+ /* copy name */
+ if ((name_end - name_start) > MAX_NAME) {
+ uc_error("error: %s:%d name too big at %d chars",
+ file, line, name_end - name_start);
+ return -EINVAL;
+ }
+ strncpy(name, name_start, name_end - name_start);
+ name[name_end - name_start] = 0;
+ strncpy(control->name, name, name_end - name_start +1);
+
+ /* get count */
+ uc_dbg("%s", tbuf);
+ tbuf++;
+ count = atoi(tbuf);
+ if (count == 0) {
+ uc_error("error: %s:%d count == 0 on line %d", file, line);
+ return -EINVAL;
+ }
+ control->count = count;
+
+ /* get vals */
+ control->value = calloc(count, sizeof(unsigned short));
+ if (control->value == NULL)
+ return -ENOMEM;
+
+ while (*tbuf != 0 && *tbuf != ':')
+ tbuf++;
+ if (*tbuf == 0)
+ return -EINVAL;
+ tbuf++;
+
+ for (i = 0; i < count; i++) {
+ set_value(control, i, atoi(tbuf));
+ while (*tbuf != 0 && *tbuf != ',')
+ tbuf++;
+
+ if (*tbuf++ == 0 && i < (count - 1))
+ return -EINVAL;
+ }
+
+ return get_control_id(uc_mgr, control);
+}
+
+static int parse_controls(snd_use_case_mgr_t *uc_mgr, FILE *f, int *line_,
+ char *file)
+{
+ struct control_settings *control = NULL;
+ char buf[MAX_BUF], name[MAX_NAME];
+ int count, i, ret = 0, line = *line_;
+ char *name_start, *name_end, *tbuf;
+
+ while (fgets(buf, MAX_BUF, f) != NULL) {
+
+ uc_dbg("%s: get line %d\n%s", file, line, buf);
+
+ tbuf = buf;
+ while (isblank(*tbuf))
+ tbuf++;
+ line ++;
+
+ /* end of section ?*/
+ if (strncmp(tbuf, "EndSectionDefaults", 18) == 0)
+ return 0;
+
+ /* get name start */
+ while (*tbuf != 0 && *tbuf != '\'')
+ tbuf++;
+ if (*tbuf == 0)
+ return -EINVAL;
+ name_start = ++tbuf;
+
+ /* get name end */
+ while (*tbuf != 0 && *tbuf != '\'')
+ tbuf++;
+ if (*tbuf == 0)
+ return -EINVAL;
+ name_end = tbuf++;
+
+ /* copy name */
+ if ((name_end - name_start) > MAX_NAME) {
+ uc_error("error: %s:%d name too big at %d chars",
+ file, line, name_end - name_start);
+ return -EINVAL;
+ }
+ strncpy(name, name_start, name_end - name_start);
+ name[name_end - name_start] = 0;
+
+ for (i = 0; i < uc_mgr->count; i++) {
+ struct control_settings *card_control =
+ &uc_mgr->control[i];
+ if (!strcmp(card_control->name, name))
+ control = &uc_mgr->control[i];
+ }
+
+ uc_dbg("control id %d", control->id);
+
+ /* get count */
+ tbuf++;
+ count = atoi(tbuf);
+ if (count == 0) {
+ uc_error("error: %s:%d count == 0", file, line);
+ return -EINVAL;
+ }
+ if (count != control->count) {
+ uc_error("error: %s:%d count %d does not match card count"
+ " %d", file, line, count, control->count);
+ return -EINVAL;
+ }
+
+ /* get vals */
+ control->value = malloc(control->count *uc_mgr->num_verbs *
+ sizeof(unsigned short));
+ if (control->value == NULL)
+ return -ENOMEM;
+
+ while (*tbuf != 0 && *tbuf != ':')
+ tbuf++;
+ if (*tbuf == 0)
+ return -EINVAL;
+ tbuf++;
+
+ for (i = 0; i < count; i++) {
+ set_value(control, i, atoi(tbuf));
+ while (*tbuf != 0 && *tbuf != ',')
+ tbuf++;
+
+ if (*tbuf++ == 0 && i < (count - 1))
+ return -EINVAL;
+ }
+ }
+
+ *line_ = line;
+ return ret;
+}
+
+static char *get_string (char *buf)
+{
+ char *str, *end;
+
+ uc_dbg("%s", buf);
+
+ while (isblank(*buf))
+ buf++;
+
+ /* find leading '"' */
+ if (*buf == 0 || *buf != '"') {
+ uc_error("error: missing start '\"'");
+ return NULL;
+ }
+ str = ++buf;
+
+ /* get value */
+ while (*buf != 0 && *buf != '"')
+ buf++;
+ end = buf;
+
+ /* find '"' terminator */
+ if (*buf == 0 || *buf != '"') {
+ uc_error("error: missing terminator '\"' %s", buf);
+ return NULL;
+ }
+
+ *end = 0;
+ return strdup(str);
+}
+
+static enum snd_use_case_qos get_enum (char *buf)
+{
+ while (isblank(*buf))
+ buf++;
+
+ if (!strncmp(buf, "Music", 5))
+ return SND_USE_CASE_QOS_MUSIC;
+ if (!strncmp(buf, "Voice", 5))
+ return SND_USE_CASE_QOS_VOICE;
+ if (!strncmp(buf, "Tones", 5))
+ return SND_USE_CASE_QOS_TONES;
+
+ return SND_USE_CASE_QOS_MUSIC;
+}
+
+static void seq_list_append(struct sequence_element **base,
+ struct sequence_element *curr)
+{
+ struct sequence_element *last, *tmp;
+
+ if (!*base)
+ *base = curr;
+ else {
+ tmp = *base;
+ while (tmp) {
+ last = tmp;
+ tmp = tmp->next;
+ }
+ last->next = curr;
+ curr->next = NULL;
+ }
+}
+/*
+ * Parse sequences.
+ *
+ * Sequence controls elements are in the following form:-
+ *
+ * 'name':value0,value1,...,valueN
+ *
+ * e.g.
+ * 'Master Playback Switch':0,0
+ */
+static int parse_sequence(snd_use_case_mgr_t *uc_mgr,
+ struct sequence_element **base, FILE *f,
+ int *line_, char *file)
+{
+ char buf[MAX_BUF], *tbuf;
+ int ret, line = *line_;
+ struct sequence_element *curr;
+
+ /* read line by line */
+ while (fgets(buf, MAX_BUF, f) != NULL) {
+
+ uc_dbg("%s", buf);
+ line++;
+
+ /* Check for lines with comments and ignore */
+ if (buf[0] == '#')
+ continue;
+
+ /* Parse current line and skip blanks */
+ tbuf = buf;
+ while (isblank(*tbuf))
+ tbuf++;
+
+ /* end of sequence ? */
+ if (strncmp(tbuf, "EndSequence", 11) == 0)
+ goto out;
+
+ if (strncmp(tbuf, "EndTransition", 13) == 0)
+ goto out;
+
+ /* alloc new sequence element */
+ curr = calloc(1, sizeof(struct sequence_element));
+ if (curr == NULL)
+ return -ENOMEM;
+
+ /* is element a sleep ? */
+ if (strncmp(tbuf, "msleep", 6) == 0) {
+ curr->sleep = atoi(tbuf + 6);
+ uc_dbg("msleep %d", curr->sleep);
+ seq_list_append(base, curr);
+ continue;
+ }
+
+ /* alloc new sequence control */
+ curr->control = calloc(1, sizeof(struct control_settings));
+ if (curr->control == NULL)
+ return -ENOMEM;
+
+ ret = parse_control(uc_mgr, curr->control, tbuf, line, file);
+ if (ret < 0) {
+ uc_error("error: %s:%d failed to get parse sequence"
+ " controls", file, line);
+ goto err;
+ }
+
+ uc_dbg("name %s, id %d, count %d", curr->control->name,
+ curr->control->id, curr->control->count);
+ seq_list_append(base, curr);
+ }
+
+out:
+ *line_ = line;
+ return 0;
+
+err:
+ free(curr);
+ return ret;
+}
+
+static void prepend_transition(
+ struct transition_sequence **transition_list,
+ struct transition_sequence *trans_seq)
+{
+ if (*transition_list == NULL)
+ *transition_list = trans_seq;
+ else {
+ trans_seq->next = *transition_list;
+ *transition_list = trans_seq;
+ }
+
+}
+
+static void prepend_dev(struct dev_list **dev_list,
+ struct dev_list *sdev)
+{
+ if (*dev_list == NULL)
+ *dev_list = sdev;
+ else {
+ sdev->next = *dev_list;
+ *dev_list = sdev;
+ }
+
+}
+
+/*
+ * Parse Modifier Use cases
+ *
+ * # Each modifier is described in new section. N modifier are allowed
+ * SectionModifier
+ *
+ * Name "Capture Voice"
+ * Comment "Record voice call"
+ * SupportedDevice "x"
+ * SupportedDevice "y"
+ *
+ * EnableSequence
+ * ....
+ * EndSequence
+ *
+ * DisableSequence
+ * ...
+ * EndSequence
+ *
+ * # Optional QoS and ALSA PCMs
+ * QoS Voice
+ * CapturePCM 1
+ * MasterPlaybackVolume 'Device Master Playback Volume'
+ * MasterPlaybackSwitch 'Device Master Playback Switch'
+ *
+ * EndSection
+ */
+static int parse_modifier(snd_use_case_mgr_t *uc_mgr,
+ struct use_case_verb *verb, FILE *f, int *line_, char *file)
+{
+ struct use_case_modifier *modifier;
+ int line = *line_, end = 0, en_seq = 0, dis_seq = 0;
+ int id = 0, dev = 0, ret;
+ char buf[MAX_BUF], *tbuf;
+ char *name = NULL, *comment = NULL;
+
+ /* allocate modifier */
+ verb->modifier = realloc(verb->modifier,
+ (verb->num_modifiers + 1) * sizeof(struct use_case_modifier));
+ if (verb->modifier == NULL)
+ return -ENOMEM;
+ modifier = verb->modifier + verb->num_modifiers;
+ bzero(modifier, sizeof(struct use_case_modifier));
+
+ /* read line by line */
+ while(fgets(buf, MAX_BUF, f) != NULL) {
+
+ line++;
+
+ /* skip comments */
+ if (buf[0] == '#')
+ continue;
+
+ tbuf = buf;
+ /* skip spaces */
+ while (isblank(*tbuf))
+ tbuf++;
+
+ /* get use case modifier name */
+ if (strncmp(tbuf, "Name", 4) == 0) {
+ name = get_string(tbuf + 4);
+ if (name == NULL) {
+ uc_error("error: %s:%d failed to get modifier name",
+ file, line);
+ goto err;
+ }
+ modifier->name = name;
+ id = 1;
+ continue;
+ }
+
+ if (strncmp(tbuf, "Comment", 8) == 0) {
+ name = get_string(tbuf + 8);
+ if (name == NULL) {
+ uc_error("error: %s:%d failed to get modifier comment",
+ file, line);
+ goto err;
+ }
+ modifier->comment = comment;
+ continue;
+ }
+
+ if (strncmp(tbuf, "SupportedDevice", 15) == 0) {
+ struct dev_list *sdev;
+ name = get_string(tbuf + 15);
+ if (name == NULL) {
+ uc_error("error: %s:%d failed to get modifier"
+ " supported device", file, line);
+ goto err;
+ }
+
+ sdev = calloc(1, sizeof(struct dev_list));
+ if (sdev == NULL)
+ goto err;
+
+ dev = 1;
+ sdev->name = name;
+ prepend_dev(&modifier->dev_list, sdev);
+ continue;
+ }
+
+ if (strncmp(tbuf, "EnableSequence", 14) == 0) {
+ ret = parse_sequence(uc_mgr, &modifier->enable, f,
+ &line, file);
+ if (ret < 0) {
+ uc_error("error: %s:%d failed to parse modifier"
+ " enable sequence", file, line);
+ goto err;
+ }
+ en_seq = 1;
+ continue;
+ }
+
+ if (strncmp(tbuf, "DisableSequence", 15) == 0) {
+ ret = parse_sequence(uc_mgr, &modifier->disable, f,
+ &line, file);
+ if (ret < 0) {
+ uc_error("error: %s:%d failed to parse modifier"
+ " disable sequence", file, line);
+ goto err;
+ }
+ dis_seq = 1;
+ continue;
+ }
+
+ if (strncmp(tbuf, "TransitionModifier", 16) == 0) {
+ struct transition_sequence *trans_seq;
+
+ name = get_string(tbuf + 16);
+ if (name == NULL)
+ continue;
+
+ trans_seq = calloc(1, sizeof(struct transition_sequence));
+ if (trans_seq == NULL)
+ return -ENOMEM;
+
+ trans_seq->name = name;
+
+ ret = parse_sequence(uc_mgr, &trans_seq->transition, f,
+ &line, file);
+ if (ret < 0) {
+ uc_error("error: %s:%d failed to parse transition"
+ " modifier", file, line);
+ goto err;
+ }
+
+ prepend_transition(&modifier->transition_list, trans_seq);
+ continue;
+ }
+
+ if (strncmp(tbuf, "QoS", 3) == 0) {
+ modifier->qos = get_enum(tbuf + 3);
+ continue;
+ }
+
+ if (strncmp(tbuf, "CapturePCM", 3) == 0) {
+ modifier->capture_pcm = atoi(tbuf + 3);
+ if (modifier->capture_pcm < 0) {
+ uc_error("error: %s:%d failed to get Capture PCM ID",
+ file, line);
+ goto err;
+ }
+ continue;
+ }
+
+ if (strncmp(tbuf, "PlaybackPCM", 3) == 0) {
+ modifier->playback_pcm = atoi(tbuf + 3);
+ if (modifier->playback_pcm < 0) {
+ uc_error("error: %s:%d failed to get Playback PCM ID",
+ file, line);
+ goto err;
+ }
+ continue;
+ }
+
+ if (strncmp(tbuf, "MasterPlaybackVolume", 20) == 0) {
+ modifier->playback_volume_id =
+ get_control_name(tbuf + 20, line, file);
+ if (modifier->playback_volume_id == NULL) {
+ uc_error("error: %s:%d failed to get MasterPlaybackVolume",
+ file, line);
+ goto err;
+ }
+ continue;
+ }
+
+ if (strncmp(tbuf, "MasterPlaybackSwitch", 20) == 0) {
+ modifier->playback_switch_id =
+ get_control_name(tbuf + 20, line, file);
+ if (modifier->playback_switch_id == NULL) {
+ uc_error("error: %s:%d failed to get MasterPlaybackSwitch",
+ file, line);
+ goto err;
+ }
+ continue;
+ }
+
+ if (strncmp(tbuf, "MasterCaptureVolume", 19) == 0) {
+ modifier->capture_volume_id =
+ get_control_name(tbuf + 19, line, file);
+ if (modifier->capture_volume_id == NULL) {
+ uc_error("error: %s:%d failed to get MasterCaptureVolume",
+ file, line);
+ goto err;
+ }
+ continue;
+ }
+
+ if (strncmp(tbuf, "MasterCaptureSwitch", 19) == 0) {
+ modifier->capture_switch_id =
+ get_control_name(tbuf + 19, line, file);
+ if (modifier->capture_switch_id == NULL) {
+ uc_error("error: %s:%d failed to get MasterCaptureSwitch",
+ file, line);
+ goto err;
+ }
+ continue;
+ }
+
+ /* end of section ?*/
+ if (strncmp(tbuf, "EndSection", 10) == 0) {
+ end = 1;
+ break;
+ }
+ }
+
+ /* do we have the modifier basics ? */
+ if (!en_seq || !dis_seq || !end || !dev) {
+ uc_error("error: invalid modifier");
+ if (!en_seq)
+ uc_error("error: %s: modifier missing enable sequence",
+ file);
+ if (!dis_seq)
+ uc_error("error: %s: modifier missing disable sequence",
+ file);
+ if (!dev)
+ uc_error("error: %s: modifier missing supported device"
+ " sequence", file);
+ if (!end)
+ uc_error("error: %s: modifier missing end", file);
+ return -EINVAL;
+ }
+
+ verb->modifier_list[verb->num_modifiers++] = modifier->name;
+ *line_ = line;
+ return 0;
+
+err:
+ if (ferror(f)) {
+ uc_error("error: %s: failed to read modifier master section",
+ file);
+ return ferror(f);
+ }
+ return -ENOMEM;
+}
+
+/*
+ * Parse Device Use Cases
+ *
+ *# Each device is described in new section. N devices are allowed
+ *SectionDevice
+ *
+ * Name "Headphones"
+ * Comment "Headphones connected to 3.5mm jack"
+ * Index 0
+ *
+ * EnableSequence
+ * ....
+ * EndSequence
+ *
+ * DisableSequence
+ * ...
+ * EndSequence
+ *
+ * #Optional control aliases
+ * MasterPlaybackVolume 'Device Master Playback Volume'
+ * MasterPlaybackSwitch 'Device Master Playback Switch'
+ *
+ * EndSection
+ */
+static int parse_device(snd_use_case_mgr_t *uc_mgr,
+ struct use_case_verb *verb, FILE *f, int *line_, char *file)
+{
+ struct use_case_device *device;
+ int line = *line_, end = 0, en_seq = 0, dis_seq = 0, id = 0, ret;
+ char buf[MAX_BUF], *tbuf;
+ char *name = NULL, *comment;
+
+ /* allocate device */
+ verb->device = realloc(verb->device,
+ (verb->num_devices + 1) * sizeof(struct use_case_device));
+ if (verb->device == NULL)
+ return -ENOMEM;
+ device = verb->device + verb->num_devices;
+ bzero(device, sizeof(struct use_case_device));
+
+ /* read line by line */
+ while(fgets(buf, MAX_BUF, f) != NULL) {
+
+ line++;
+
+ /* skip comments */
+ if (buf[0] == '#')
+ continue;
+
+ tbuf = buf;
+ /* skip spaces */
+ while (isblank(*tbuf))
+ tbuf++;
+
+ /* get use case device name */
+ if (strncmp(tbuf, "Name", 4) == 0) {
+ name = get_string(tbuf + 4);
+ if (name == NULL) {
+ uc_error("error: %s:%d failed to get device name",
+ file, line);
+ goto err;
+ }
+ device->name = name;
+ id = 1;
+ continue;
+ }
+
+ if (strncmp(tbuf, "Comment", 8) == 0) {
+ comment = get_string(tbuf + 8);
+ if (name == NULL) {
+ uc_error("error: %s: %d failed to get device comment",
+ file, line);
+ goto err;
+ }
+
+ device->comment = comment;
+ continue;
+ }
+
+ if (strncmp(tbuf, "Index", 5) == 0) {
+ device->idx = atoi(tbuf + 5);
+ if (device->idx < 0) {
+ uc_error("error: %s:%d failed to get device index",
+ file, line);
+ goto err;
+ }
+ continue;
+ }
+
+ if (strncmp(tbuf, "EnableSequence", 14) == 0) {
+ ret = parse_sequence(uc_mgr, &device->enable, f,
+ &line, file);
+ if (ret < 0) {
+ uc_error("error: %s:%d failed to parse device enable"
+ " sequence", file, line);
+ goto err;
+ }
+ en_seq = 1;
+ continue;
+ }
+
+ if (strncmp(tbuf, "DisableSequence", 15) == 0) {
+ uc_dbg("DisableSequence");
+ ret = parse_sequence(uc_mgr, &device->disable, f,
+ &line, file);
+ if (ret < 0) {
+ uc_error("error: %s:%d failed to parse device disable sequence",
+ file, line);
+ goto err;
+ }
+ dis_seq = 1;
+ continue;
+ }
+
+ if (strncmp(tbuf, "TransitionDevice", 14) == 0) {
+ struct transition_sequence *trans_seq;
+
+ name = get_string(tbuf + 14);
+ if (name == NULL)
+ continue;
+
+ uc_dbg("TransitionDevice %s", name);
+
+ trans_seq = calloc(1, sizeof(struct transition_sequence));
+ if (trans_seq == NULL)
+ return -ENOMEM;
+
+ trans_seq->name = name;
+
+ ret = parse_sequence(uc_mgr, &trans_seq->transition, f,
+ &line, file);
+ if (ret < 0) {
+ uc_error("error: %s:%d failed to parse transition"
+ " device", file, line);
+ goto err;
+ }
+
+ prepend_transition(&device->transition_list, trans_seq);
+ continue;
+ }
+
+ if (strncmp(tbuf, "MasterPlaybackVolume", 20) == 0) {
+ device->playback_volume_id =
+ get_control_name(tbuf + 20, line, file);
+ if (device->playback_volume_id == NULL) {
+ uc_error("error: %s:%d failed to get MasterPlaybackVolume",
+ file, line);
+ goto err;
+ }
+ continue;
+ }
+
+ if (strncmp(tbuf, "MasterPlaybackSwitch", 20) == 0) {
+ device->playback_switch_id =
+ get_control_name(tbuf + 20, line, file);
+ if (device->playback_switch_id == NULL) {
+ uc_error("error: %s:%d failed to get MasterPlaybackSwitch",
+ file, line);
+ goto err;
+ }
+ continue;
+ }
+
+ if (strncmp(tbuf, "MasterCaptureVolume", 19) == 0) {
+ device->capture_volume_id =
+ get_control_name(tbuf + 19, line, file);
+ if (device->capture_volume_id == NULL) {
+ uc_error("error: %s:%d failed to get MasterCaptureVolume%d",
+ file, line);
+ goto err;
+ }
+ continue;
+ }
+
+ if (strncmp(tbuf, "MasterCaptureSwitch", 19) == 0) {
+ device->capture_switch_id =
+ get_control_name(tbuf + 19, line, file);
+ if (device->capture_switch_id == NULL) {
+ uc_error("error: %s:%d failed to get MasterCaptureSwitch",
+ file, line);
+ goto err;
+ }
+ continue;
+ }
+
+ /* end of section ?*/
+ if (strncmp(tbuf, "EndSection", 10) == 0) {
+ end = 1;
+ break;
+ }
+ }
+
+ /* do we have the basics for this device ? */
+ if (!en_seq || !dis_seq || !end || !id) {
+ uc_error("error: invalid device");
+ if (!en_seq)
+ uc_error("error: %s: device missing enable sequence", file);
+ if (!dis_seq)
+ uc_error("error: %s: device missing disable sequence", file);
+ if (!end)
+ uc_error("error: %s: device missing end", file);
+ return -EINVAL;
+ }
+
+ verb->device_list[verb->num_devices++] = device->name;
+ *line_ = line;
+ return 0;
+
+err:
+ if (ferror(f)) {
+ uc_error("%s: failed to read device section", file);
+ return ferror(f);
+ }
+ return -ENOMEM;
+}
+
+/*
+ * Parse Verb Section
+ *
+ * # Example Use case verb section for Voice call blah
+ * # By Joe Blogs <joe@blogs.com>
+ *
+ * SectionVerb
+ * # enable and disable sequences are compulsory
+ * EnableSequence
+ * 'Master Playback Switch':2:0,0
+ * 'Master Playback Volume':2:25,25
+ * msleep 50
+ * 'Master Playback Switch':2:1,1
+ * 'Master Playback Volume':2:50,50
+ * EndSequence
+ *
+ * DisableSequence
+ * 'Master Playback Switch':2:0,0
+ * 'Master Playback Volume':2:25,25
+ * msleep 50
+ * 'Master Playback Switch':2:1,1
+ * 'Master Playback Volume':2:50,50
+ * EndSequence
+ *
+ * # Optional QoS and ALSA PCMs
+ * QoS HiFi
+ * CapturePCM 0
+ * PlaybackPCM 0
+ *
+ * EndSection
+ */
+static int parse_verb(snd_use_case_mgr_t *uc_mgr,
+ struct use_case_verb *verb, FILE *f, int *line_, char *file)
+{
+ int line = *line_, end = 0, en_seq = 0, dis_seq = 0, ret;
+ char buf[MAX_BUF], *tbuf;
+
+ /* read line by line */
+ while(fgets(buf, MAX_BUF, f) != NULL) {
+
+ line++;
+
+ /* skip comments */
+ if (buf[0] == '#')
+ continue;
+
+ tbuf = buf;
+ /* skip spaces */
+ while (isblank(*tbuf))
+ tbuf++;
+
+ if (strncmp(tbuf, "EnableSequence", 14) == 0) {
+ uc_dbg("Parse EnableSequence");
+ ret = parse_sequence(uc_mgr, &verb->enable, f, &line, file);
+ if (ret < 0) {
+ uc_error("error: %s:%d failed to parse verb enable sequence",
+ file, line);
+ goto err;
+ }
+ en_seq = 1;
+ continue;
+ }
+
+ if (strncmp(tbuf, "DisableSequence", 15) == 0) {
+ uc_dbg("Parse DisableSequence");
+ ret = parse_sequence(uc_mgr, &verb->disable, f, &line, file);
+ if (ret < 0) {
+ uc_error("error: %s:%d failed to parse verb disable sequence",
+ file, line);
+ goto err;
+ }
+ dis_seq = 1;
+ continue;
+ }
+
+ if (strncmp(tbuf, "TransitionVerb", 12) == 0) {
+ struct transition_sequence *trans_seq;
+ char *name;
+
+ name = get_string(tbuf + 12);
+ if (name == NULL)
+ continue;
+
+ uc_dbg("TransitionVerb %s", name);
+
+ trans_seq = calloc(1, sizeof(struct transition_sequence));
+ if (trans_seq == NULL)
+ return -ENOMEM;
+
+ trans_seq->name = name;
+
+ ret = parse_sequence(uc_mgr, &trans_seq->transition, f,
+ &line, file);
+ if (ret < 0) {
+ uc_error("error: %s:%d failed to parse transition verb",
+ file, line);
+ goto err;
+ }
+
+ prepend_transition(&verb->transition_list, trans_seq);
+ continue;
+ }
+
+ if (strncmp(tbuf, "QoS", 3) == 0) {
+ uc_dbg("Parse Qos");
+ verb->qos = get_enum(tbuf + 3);
+ continue;
+ }
+
+ if (strncmp(tbuf, "CapturePCM", 3) == 0) {
+ uc_dbg("Parse CapTurePCM");
+ verb->capture_pcm = atoi(tbuf + 3);
+ if (verb->capture_pcm < 0) {
+ uc_error("error: %s:%d failed to get Capture PCM ID",
+ file, line);
+ goto err;
+ }
+ continue;
+ }
+
+ if (strncmp(tbuf, "PlaybackPCM", 3) == 0) {
+ uc_dbg("Parse PlaybackPCM");
+ verb->playback_pcm = atoi(tbuf + 3);
+ if (verb->playback_pcm < 0) {
+ uc_error("error: %s: %d failed to get Playback PCM ID",
+ file, line);
+ goto err;
+ }
+ continue;
+ }
+
+ /* end of section ?*/
+ if (strncmp(tbuf, "EndSection", 10) == 0) {
+ end = 1;
+ break;
+ }
+ }
+
+ /* do we have both use case name and file ? */
+ if (!en_seq || !dis_seq || !end) {
+ uc_error("error: invalid verb");
+ if (!en_seq)
+ uc_error("error: %s: verb missing enable sequence", file);
+ if (!dis_seq)
+ uc_error("error: %s: verb missing disable sequence", file);
+ if (!end)
+ uc_error("error: %s: verb missing end", file);
+ return -EINVAL;
+ }
+
+ *line_ = line;
+ return 0;
+
+err:
+ if (ferror(f)) {
+ uc_error("error: failed to read verb master section");
+ return ferror(f);
+ }
+ return -ENOMEM;
+}
+
+/*
+ * Parse a Use case verb file.
+ *
+ * This file contains the following :-
+ * o Verb enable and disable sequences.
+ * o Supported Device enable and disable sequences for verb.
+ * o Supported Modifier enable and disable sequences for verb
+ * o Optional QoS for the verb and modifiers.
+ * o Optional PCM device ID for verb and modifiers
+ * o Alias kcontrols IDs for master and volumes and mutes.
+ */
+static int parse_verb_file(snd_use_case_mgr_t *uc_mgr,
+ char *use_case_name, char *file)
+{
+ struct use_case_verb *verb;
+ FILE *f;
+ int line = 0, ret = 0, dev_idx = 0, verb_idx = 0, mod_idx = 0;
+ char buf[MAX_BUF], *tbuf;
+ char filename[MAX_FILE];
+
+ /* allocate verb */
+ uc_mgr->verb = realloc(uc_mgr->verb,
+ (uc_mgr->num_verbs + 1) * sizeof(struct use_case_verb));
+ if (uc_mgr->verb == NULL)
+ return -ENOMEM;
+ verb = uc_mgr->verb +uc_mgr->num_verbs;
+ bzero(verb, sizeof(struct use_case_verb));
+
+ /* open Verb file for reading */
+ sprintf(filename, "%s/%s/%s", ALSA_USE_CASE_DIR,
+ uc_mgr->card_name, file);
+
+ f = fopen(filename, "r");
+ if (f == NULL) {
+ uc_error("error: failed to open verb file %s : %d",
+ filename, -errno);
+ return -errno;
+ }
+
+ /* read line by line */
+ while(fgets(buf, MAX_BUF, f) != NULL) {
+
+ line++;
+
+ /* skip comments */
+ if (buf[0] == '#')
+ continue;
+
+ /* skip leading spaces */
+ tbuf = buf;
+ while (isblank(*tbuf))
+ tbuf++;
+
+ /* find verb section and parse it */
+ if (strncmp(tbuf, "SectionVerb", 11) == 0) {
+ ret = parse_verb(uc_mgr, verb, f, &line, file);
+ if (ret < 0) {
+ uc_error("error: %s:%d failed to parse verb %s",
+ file, line, use_case_name);
+ goto err;
+ }
+ verb_idx++;
+ continue;
+ }
+
+ /* find device sections and parse them */
+ if (strncmp(tbuf, "SectionDevice", 13) == 0) {
+
+ if (verb->num_devices >= MAX_DEVICE) {
+ uc_error("error: %s:%d verb number of devices %d"
+ "exceeds max %d", file, line,
+ verb->num_devices, MAX_DEVICE);
+ goto err;
+ }
+ ret = parse_device(uc_mgr, verb, f, &line, file);
+ if (ret < 0) {
+ uc_error("error: %s:%d failed to parse device",
+ file, line);
+ goto err;
+ }
+ dev_idx++;
+ continue;
+ }
+
+ /* find modifier sections and parse them */
+ if (strncmp(tbuf, "SectionModifier", 15) == 0) {
+ if (verb->num_modifiers >= MAX_MODIFIER) {
+ uc_error("error: %s:%d verb number of modifiers %d"
+ " exceeds max %d", file, line,
+ verb->num_modifiers, MAX_MODIFIER);
+ goto err;
+ }
+
+ ret = parse_modifier(uc_mgr, verb, f, &line, file);
+ if (ret < 0) {
+ uc_error("error: %s:%d failed to parse modifier",
+ file, line);
+ goto err;
+ }
+ mod_idx++;
+ continue;
+ }
+ }
+
+ /* use case verb must have at least verb and 1 device */
+ if (verb_idx && dev_idx) {
+ uc_mgr->verb_list[uc_mgr->num_verbs++] = use_case_name;
+ verb->name = use_case_name;
+ }
+ else {
+ uc_error("error: failed to parse use case %s", file);
+ if (verb_idx == 0)
+ uc_error("error: no use case verb defined", file);
+ if (dev_idx == 0)
+ uc_error("error: no use case device defined", file);
+ ret = -EINVAL;
+ }
+
+err:
+ fclose(f);
+ return ret;
+}
+
+/*
+ * Parse master section for "Use Case" and "File" tags.
+ */
+static int parse_master_section(snd_use_case_mgr_t *uc_mgr, FILE *f,
+ int *line_)
+{
+ int line = *line_ - 1, end = 0;
+ char buf[MAX_BUF], *tbuf;
+ char *file = NULL, *use_case_name = NULL, *comment;
+
+ /* read line by line */
+ while(fgets(buf, MAX_BUF, f) != NULL) {
+
+ line++;
+
+ /* skip comments */
+ if (buf[0] == '#')
+ continue;
+
+ uc_dbg("%s", buf);
+
+ /* skip leading spaces */
+ tbuf = buf;
+ while (isblank(*tbuf))
+ tbuf++;
+
+ /* get use case name */
+ if (strncmp(tbuf, "Use Case", 8) == 0) {
+ use_case_name = get_string(tbuf + 8);
+ if (use_case_name == NULL) {
+ uc_error("error: failed to get Use Case at line %d",
+ line);
+ goto err;
+ }
+
+ if (uc_mgr->num_verbs >= MAX_VERB) {
+ uc_error("error: verb number exceed max %d",
+ uc_mgr->num_verbs, MAX_VERB);
+ goto err;
+ }
+ continue;
+ }
+
+ /* get use case verb file name */
+ if (strncmp(tbuf, "File", 4) == 0) {
+ file = get_string(tbuf + 4);
+ if (file == NULL) {
+ uc_error("error: failed to get File at line %d", line);
+ goto err;
+ }
+ continue;
+ }
+
+ /* get optional use case comment */
+ if (strncmp(tbuf, "Comment", 7) == 0) {
+ comment = get_string(tbuf + 7);
+ if (comment == NULL) {
+ uc_error("error: failed to get Comment at line %d",
+ line);
+ goto err;
+ }
+ continue;
+ }
+
+ /* end of section ?*/
+ if (strncmp(tbuf, "EndSectionUseCase", 10) == 0) {
+ end = 1;
+ break;
+ }
+ }
+
+ uc_dbg("use_case_name %s file %s end %d", use_case_name, file, end);
+
+ /* do we have both use case name and file ? */
+ if (!use_case_name || !file || !end) {
+ uc_error("error: failed to find use case\n");
+ if (!use_case_name)
+ uc_error("error: use case missing name");
+ if (!file)
+ uc_error("error: use case missing file");
+ if (!end)
+ uc_error("error: use case missing end");
+ return -EINVAL;
+ }
+
+ *line_ = line;
+
+ /* parse verb file */
+ return parse_verb_file(uc_mgr, use_case_name, file);
+
+err:
+ if (ferror(f)) {
+ uc_error("error: failed to read use case master section");
+ return ferror(f);
+ }
+ return -ENOMEM;
+}
+
+/*
+ * Each sound card has a master sound card file that lists all the supported
+ * use case verbs for that sound card. i.e.
+ *
+ * #Example master file for blah sound card
+ * #By Joe Blogs <joe@bloggs.org>
+ *
+ * # The file is divided into Use case sections. One section per use case verb.
+ *
+ * SectionUseCase
+ * Use Case "Voice Call"
+ * File "voice_call_blah"
+ * Comment "Make a voice phone call."
+ * EndSectionUseCase
+ *
+ * SectionUseCase
+ * Use Case "HiFi"
+ * File "hifi_blah"
+ * Comment "Play and record HiFi quality Music."
+ * EndSectionUseCase
+ *
+ * # This file also stores the default sound card state.
+ *
+ * SectionDefaults
+ * 'Master Playback Switch':2:1,1
+ * 'Master Playback Volume':2:25,25
+ * 'Master Mono Playback Switch':1:0
+ * 'Master Mono Playback Volume':1:0
+ * 'PCM Switch':2:1,1
+ * ........
+ * EndSectionDefaults
+ *
+ * # End of example file.
+ */
+static int parse_master_file(snd_use_case_mgr_t *uc_mgr, FILE *f,
+ char *file)
+{
+ int line = 0, ret = 0;
+ char buf[MAX_BUF], *tbuf;
+
+ /* parse master config sections */
+ while(fgets(buf, MAX_BUF, f) != NULL) {
+
+ line++;
+
+ /* ignore comments */
+ if (buf[0] == '#') {
+ line++;
+ continue;
+ }
+
+ /* find use case section and parse it */
+ if (strncmp(buf, "SectionUseCase", 14) == 0) {
+ tbuf = buf + 14;
+ while (isblank(*tbuf))
+ tbuf++;
+ ret = parse_master_section(uc_mgr, f, &line);
+ if (ret < 0)
+ goto err;
+ }
+
+ /* find default control values section and parse it */
+ if (strncmp(buf, "SectionDefaults", 15) == 0) {
+ tbuf = buf + 15;
+ while (isblank(*tbuf))
+ tbuf++;
+ ret = parse_controls(uc_mgr, f, &line, file);
+ if (ret < 0)
+ goto err;
+ }
+ }
+
+err:
+ if (ferror(f)) {
+ uc_error("error: %s: failed to read master file", file);
+ return ferror(f);
+ }
+ return ret;
+}
+
+/* load master use case file for sound card */
+static int import_master_config(snd_use_case_mgr_t *uc_mgr)
+{
+ int ret;
+ FILE *f;
+ char filename[MAX_FILE];
+
+ sprintf(filename, "%s/%s/%s.conf", ALSA_USE_CASE_DIR,
+ uc_mgr->card_name, uc_mgr->card_name);
+
+ uc_dbg("master config file %s", filename);
+
+ f = fopen(filename, "r");
+ if (f == NULL) {
+ uc_error("error: couldn't open %s configuration file %s",
+ uc_mgr->card_name, filename);
+ return -errno;
+ }
+
+ ret = parse_master_file(uc_mgr, f, uc_mgr->card_name);
+ fclose(f);
+ return ret;
+}
+
+static int parse_card_controls(snd_use_case_mgr_t *uc_mgr)
+{
+ struct control_settings *control;
+ int i, ret = 0;
+ snd_ctl_elem_id_t *id;
+
+ /* allocate memory for controls */
+ uc_mgr->control = calloc(uc_mgr->count,
+ sizeof(struct control_settings));
+ if (uc_mgr->control == NULL) {
+ uc_error("error: not enough memory to store controls.\n");
+ return -ENOMEM;
+ }
+ control = uc_mgr->control;
+ snd_ctl_elem_id_alloca(&id);
+
+ /* iterate through each kcontrol and add to manager */
+ for (i = 0; i < uc_mgr->count; ++i) {
+
+ snd_ctl_elem_list_get_id(uc_mgr->list, i, id);
+ ret = add_control(uc_mgr->handle, id, control++);
+ if (ret < 0) {
+ uc_error("error: failed to add control error %s\n",
+ __func__, snd_strerror(ret));
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static int import_use_case_files(snd_use_case_mgr_t *uc_mgr)
+{
+ int ret;
+
+ /* import the master file and default kcontrol settings */
+ ret = import_master_config(uc_mgr);
+ if (ret < 0) {
+ uc_error("error: failed to parse master use case config %s\n",
+ uc_mgr->card_name);
+ return ret;
+ }
+
+ return 0;
+}
+
+
+static void free_sequence(struct sequence_element *sequence)
+{
+ struct sequence_element *element = sequence;
+
+ while (element) {
+ struct sequence_element *pre_element;
+
+ if (element->control) {
+ free(element->control->value);
+ free(element->control);
+ }
+ pre_element = element;
+ element = element->next;
+ free(pre_element);
+ }
+}
+
+static void free_transition_sequence_element(struct transition_sequence *trans)
+{
+ free(trans->name);
+ free_sequence(trans->transition);
+ free(trans);
+}
+
+static void free_transition_sequence(struct transition_sequence *transition_list)
+{
+ struct transition_sequence *last, *trans_sequence = transition_list;
+
+ while (trans_sequence) {
+ last = trans_sequence;
+ trans_sequence = trans_sequence->next;
+ free_transition_sequence_element(last);
+ }
+}
+
+static void free_modifier(struct use_case_modifier *modifier)
+{
+ if (!modifier)
+ return;
+
+ free(modifier->name);
+ free(modifier->comment);
+ free(modifier->playback_volume_id);
+ free(modifier->playback_switch_id);
+ free(modifier->capture_volume_id);
+ free(modifier->capture_switch_id);
+
+ free_sequence(modifier->enable);
+ free_sequence(modifier->disable);
+ free_transition_sequence(modifier->transition_list);
+}
+
+static void free_device(struct use_case_device *device)
+{
+ if (!device)
+ return;
+
+ free(device->name);
+ free(device->comment);
+ free(device->playback_volume_id);
+ free(device->playback_switch_id);
+ free(device->capture_volume_id);
+ free(device->capture_switch_id);
+
+ free_sequence(device->enable);
+ free_sequence(device->disable);
+ free_transition_sequence(device->transition_list);
+}
+
+static void free_devices(struct use_case_device *devices, int num_devices)
+{
+ int i;
+
+ if (!devices)
+ return;
+
+ for (i = 0; i< num_devices; i++)
+ free_device(devices + i);
+
+ free(devices);
+}
+
+static void free_modifiers(struct use_case_modifier *modifiers,
+ int num_modifiers)
+{
+ int i;
+
+ if (!modifiers)
+ return;
+
+ for (i = 0; i < num_modifiers; i++)
+ free_modifier(modifiers + i);
+
+ free(modifiers);
+}
+
+static void free_verb(struct use_case_verb *verb)
+{
+ if (!verb)
+ return;
+
+ free(verb->name);
+ free(verb->comment);
+
+ free_sequence(verb->enable);
+ free_sequence(verb->disable);
+ free_transition_sequence(verb->transition_list);
+
+ free_devices(verb->device, verb->num_devices);
+ free_modifiers(verb->modifier, verb->num_modifiers);
+}
+
+static void free_verbs(struct use_case_verb *verbs, int num_verbs)
+{
+ int i;
+
+ if (!verbs)
+ return;
+
+ for (i = 0; i < num_verbs; i++)
+ free_verb(verbs + i);
+
+ free(verbs);
+}
+
+static void free_controls(struct control_settings *controls, int num_controls)
+{
+ int i = 0;
+
+ if (!controls)
+ return;
+
+ for(i = 0; i < num_controls; i++){
+ struct control_settings *control = controls + i;
+ free(control->value);
+ }
+ free(controls);
+}
+
+/*
+ * Free all use case manager resources.
+ * Callers holds locks.
+ */
+static void free_uc_mgr(snd_use_case_mgr_t *uc_mgr)
+{
+ if (uc_mgr == NULL)
+ return;
+
+ if (uc_mgr->info)
+ snd_ctl_card_info_free(uc_mgr->info);
+ if (uc_mgr->list)
+ snd_ctl_elem_list_free(uc_mgr->list);
+ if (uc_mgr->id)
+ snd_ctl_elem_id_free(uc_mgr->id);
+
+ if (uc_mgr->handle)
+ snd_ctl_close(uc_mgr->handle);
+
+ free(uc_mgr->card_name);
+ free(uc_mgr->ctl_name);
+
+ free_verbs(uc_mgr->verb, uc_mgr->num_verbs);
+
+ free_controls(uc_mgr->control, uc_mgr->count);
+
+ pthread_mutex_destroy(&uc_mgr->mutex);
+
+ free(uc_mgr);
+}
+
+ /**
+ * \brief Init sound card use case manager.
+ * \param uc_mgr Use case manager
+ * \return zero on success, otherwise a negative error code
+ */
+snd_use_case_mgr_t *snd_use_case_mgr_open(const char *card_name)
+{
+ snd_use_case_mgr_t *uc_mgr;
+ char ctl_name[8];
+ int err, idx;
+
+ idx = snd_card_get_index(card_name);
+ if (idx < 0) {
+ uc_error("error: can't get sound card %s: %s",
+ card_name, snd_strerror(idx));
+ return NULL;
+ }
+
+ /* create a new UCM */
+ uc_mgr = calloc(1, sizeof(snd_use_case_mgr_t));
+ if (uc_mgr == NULL)
+ return NULL;
+
+ uc_mgr->card_name = strdup(card_name);
+ if (uc_mgr->card_name == NULL) {
+ free(uc_mgr);
+ return NULL;
+ }
+ sprintf(ctl_name, "hw:%d", idx);
+ uc_mgr->ctl_name = strdup(ctl_name);
+ if (uc_mgr->ctl_name == NULL) {
+ free_uc_mgr(uc_mgr);
+ return NULL;
+ }
+ if (snd_ctl_card_info_malloc(&uc_mgr->info) < 0) {
+ free_uc_mgr(uc_mgr);
+ return NULL;
+ }
+ if (snd_ctl_elem_list_malloc(&uc_mgr->list) < 0) {
+ free_uc_mgr(uc_mgr);
+ return NULL;
+ }
+ if (snd_ctl_elem_id_malloc(&uc_mgr->id) < 0) {
+ free_uc_mgr(uc_mgr);
+ return NULL;
+ }
+
+ /* open and init CTLs */
+ err = snd_ctl_open(&uc_mgr->handle, uc_mgr->ctl_name, 0);
+ if (err) {
+ uc_error("error: can't open sound card %s: %s",
+ uc_mgr->card_name, snd_strerror(err));
+ goto err;
+ }
+
+ err = snd_ctl_card_info(uc_mgr->handle, uc_mgr->info);
+ if (err < 0) {
+ uc_error("error: can't get sound card %s control info: %s",
+ uc_mgr->card_name, snd_strerror(err));
+ goto err;
+ }
+
+ err = snd_ctl_elem_list(uc_mgr->handle, uc_mgr->list);
+ if (err < 0) {
+ uc_error("error: can't get sound card %s elements: %s",
+ uc_mgr->card_name, snd_strerror(err));
+ goto err;
+ }
+
+ uc_mgr->count = snd_ctl_elem_list_get_count(uc_mgr->list);
+ if (uc_mgr->count < 0) {
+ uc_error("error: can't get sound card %s controls %s:",
+ uc_mgr->card_name, snd_strerror(err));
+ goto err;
+ }
+
+ snd_ctl_elem_list_set_offset(uc_mgr->list, 0);
+ if (snd_ctl_elem_list_alloc_space(uc_mgr->list, uc_mgr->count) < 0) {
+ uc_error("error: could not allocate elements for %s",
+ uc_mgr->card_name);
+ goto err;
+ }
+
+ err = snd_ctl_elem_list(uc_mgr->handle, uc_mgr->list);
+ if (err < 0) {
+ uc_error("error: could not get elements for %s : %s",
+ uc_mgr->card_name, snd_strerror(err));
+ goto err;
+ }
+
+ /* get info about sound card */
+ err = parse_card_controls(uc_mgr);
+ if (err < 0) {
+ uc_error("error: failed to parse sound device %s controls %d",
+ card_name, err);
+ goto err;
+ }
+
+ /* get info on use_cases and verify against card */
+ err = import_use_case_files(uc_mgr);
+ if (err < 0) {
+ uc_error("error: failed to import %s use case configuration %d",
+ card_name, err);
+ goto err;
+ }
+
+ pthread_mutex_init(&uc_mgr->mutex, NULL);
+ uc_mgr->card.current_verb = VERB_NOT_INITIALISED;
+
+ return uc_mgr;
+
+err:
+ free_uc_mgr(uc_mgr);
+ return NULL;
+}
+
+ /**
+ * \brief Reload and reparse all use case files.
+ * \param uc_mgr Use case manager
+ * \return zero on success, otherwise a negative error code
+ */
+int snd_use_case_mgr_reload(snd_use_case_mgr_t *uc_mgr)
+{
+ int ret;
+
+ pthread_mutex_lock(&uc_mgr->mutex);
+
+ free_verbs(uc_mgr->verb, uc_mgr->num_verbs);
+ free_controls(uc_mgr->control, uc_mgr->count);
+
+ /* reload all sound card controls */
+ ret = parse_card_controls(uc_mgr);
+ if (ret <= 0) {
+ uc_error("error: failed to reload sound card controls %d\n",
+ ret);
+ free_uc_mgr(uc_mgr);
+ return -EINVAL;
+ }
+
+ /* reload all use cases */
+ uc_mgr->num_verbs = import_use_case_files(uc_mgr);
+ if (uc_mgr->num_verbs <= 0) {
+ uc_error("error: failed to reload use cases\n");
+ return -EINVAL;
+ }
+
+ pthread_mutex_unlock(&uc_mgr->mutex);
+ return ret;
+}
+
+ /**
+ * \brief Close use case manager.
+ * \param uc_mgr Use case manager
+ * \return zero on success, otherwise a negative error code
+ */
+int snd_use_case_mgr_close(snd_use_case_mgr_t *uc_mgr)
+{
+ free_uc_mgr(uc_mgr);
+
+ return 0;
+}
+
+ /**
+ * \brief Reset sound card controls to default values.
+ * \param uc_mgr Use case manager
+ * \return zero on success, otherwise a negative error code
+ */
+int snd_use_case_mgr_reset(snd_use_case_mgr_t *uc_mgr)
+{
+ int ret = 0, i;
+ struct control_settings *control;
+
+ pthread_mutex_lock(&uc_mgr->mutex);
+
+ for (i = 0; i < uc_mgr->count; i++) {
+ control = &uc_mgr->control[i];
+
+ /* Only set default value specified in master config file */
+ if (control->value == NULL)
+ continue;
+
+ ret = set_control_default(uc_mgr, control);
+ if (ret < 0)
+ goto out;
+ }
+ uc_mgr->card.current_verb = VERB_NOT_INITIALISED;
+out:
+ pthread_mutex_unlock(&uc_mgr->mutex);
+ return ret;
+}
+
+
+/*
+ * Change a sound card control to a new value.
+ */
+static int set_control(snd_ctl_t *handle, snd_ctl_elem_id_t *id,
+ snd_use_case_mgr_t *uc_mgr, unsigned short value[])
+{
+ struct control_settings *setting;
+ int ret, count, i;
+ unsigned int idnum;
+ snd_ctl_elem_info_t *info;
+ snd_ctl_elem_type_t type;
+ snd_ctl_elem_value_t *control;
+
+ snd_ctl_elem_info_alloca(&info);
+ snd_ctl_elem_value_alloca(&control);
+
+ snd_ctl_elem_info_set_id(info, id);
+ ret = snd_ctl_elem_info(handle, info);
+ if (ret < 0) {
+ uc_error("error: failed to get ctl elem info %d: ",
+ snd_strerror(ret));
+ return ret;
+ }
+
+ snd_ctl_elem_value_set_id(control, id);
+ snd_ctl_elem_read(handle, control);
+
+ idnum = snd_ctl_elem_id_get_numid(id);
+ for (i = 0; i < uc_mgr->count; i++) {
+ setting = &uc_mgr->control[i];
+ if (setting->id == idnum)
+ goto set_val;
+ }
+ uc_error("error: failed to find control at id %d", idnum);
+ return 0;
+
+set_val:
+ uc_dbg("set control %s id %d count %d", setting->name, setting->id,
+ setting->count);
+
+ type = snd_ctl_elem_info_get_type(info);
+ count = snd_ctl_elem_info_get_count(info);
+ if (count == 0)
+ return 0;
+
+ uc_dbg("type %d count %d", type, count);
+
+ switch (type) {
+ case SND_CTL_ELEM_TYPE_BOOLEAN:
+ for (i = 0; i < count; i++) {
+ uc_dbg("count %d value %u", i, *(value + i));
+ snd_ctl_elem_value_set_boolean(control, i, value[i]);
+ }
+ break;
+ case SND_CTL_ELEM_TYPE_INTEGER:
+ uc_dbg("int");
+ for (i = 0; i < count; i++) {
+ uc_dbg("count %d value %u", i, value[i]);
+ snd_ctl_elem_value_set_integer(control, i, value[i]);
+ }
+ break;
+ case SND_CTL_ELEM_TYPE_INTEGER64:
+ uc_dbg("int64");
+ for (i = 0; i < count; i++) {
+ uc_dbg("count %d value %u", i, value[i]);
+ snd_ctl_elem_value_set_integer64(control, i, value[i]);
+ }
+
+ break;
+ case SND_CTL_ELEM_TYPE_ENUMERATED:
+ uc_dbg("enumerated");
+ for (i = 0; i < count; i++) {
+ uc_dbg("count %d value %u", i, value[i]);
+ snd_ctl_elem_value_set_enumerated(control, i, value[i]);
+ }
+ break;
+ case SND_CTL_ELEM_TYPE_BYTES:
+ uc_dbg("bytes");
+ for (i = 0; i < count; i++) {
+ uc_dbg("count %d value %u", i, value[i]);
+ snd_ctl_elem_value_set_byte(control, i, value[i]);
+ }
+ break;
+ default:
+ break;
+ }
+
+ ret = snd_ctl_elem_write(handle, control);
+ if (ret < 0) {
+ uc_error("error: failed to set control %s: %s",
+ setting->name, snd_strerror(ret));
+ uc_error("error: count %d type: %d",
+ count, type);
+ for (i = 0; i < count; i++)
+ fprintf(stderr, "%d ", get_value(setting, i));
+ return ret;
+ }
+ return 0;
+}
+
+/*
+ * Execute a sequence of control writes.
+ */
+static int exec_sequence(struct sequence_element *seq, snd_use_case_mgr_t
+ *uc_mgr, snd_ctl_elem_list_t *list, snd_ctl_t *handle)
+{
+ int count = snd_ctl_elem_list_get_count(list);
+ int ret, i;
+
+ uc_dbg("");
+
+ /* keep going until end of sequence */
+ while (seq) {
+ /* do we need to sleep */
+ if (seq->sleep) {
+ uc_dbg("msleep %d", seq->sleep);
+ usleep(seq->sleep);
+ } else {
+ uc_dbg("control name %s, id %d, count %d, vale[1] %u",
+ seq->control->name, seq->control->id,
+ seq->control->count, seq->control->value[0]);
+
+ /* control write */
+ snd_ctl_elem_id_t *id;
+ snd_ctl_elem_id_alloca(&id);
+ unsigned int numid;
+
+ /* Where is id lookup from numid if you need it? */
+ for (i = 0; i < count; ++i) {
+
+ snd_ctl_elem_list_get_id(list, i, id);
+ numid = snd_ctl_elem_id_get_numid(id);
+
+ if (numid == seq->control->id) {
+ ret = set_control(handle, id, uc_mgr, seq->control->value);
+ if (ret < 0) {
+ uc_error("error: failed to set control %s",
+ __func__, uc_mgr->card_name);
+ return ret;
+ }
+ break;
+ }
+ }
+ }
+ seq = seq->next;
+ }
+ return 0;
+}
+
+static int enable_use_case_verb(snd_use_case_mgr_t *uc_mgr, int verb_id,
+ snd_ctl_elem_list_t *list, snd_ctl_t *handle)
+{
+ struct use_case_verb *verb;
+ int ret;
+
+ if (verb_id >= uc_mgr->num_verbs) {
+ uc_error("error: invalid verb id %d", verb_id);
+ return -EINVAL;
+ }
+ verb = &uc_mgr->verb[verb_id];
+
+ uc_dbg("verb %s", verb->name);
+ ret = exec_sequence(verb->enable, uc_mgr, list, handle);
+ if (ret < 0) {
+ uc_error("error: could not enable verb %s", verb->name);
+ return ret;
+ }
+ uc_mgr->card.current_verb = verb_id;
+
+ return 0;
+}
+
+static int disable_use_case_verb(snd_use_case_mgr_t *uc_mgr, int verb_id,
+ snd_ctl_elem_list_t *list, snd_ctl_t *handle)
+{
+ struct use_case_verb *verb;
+ int ret;
+
+ if (verb_id >= uc_mgr->num_verbs) {
+ uc_error("error: invalid verb id %d", verb_id);
+ return -EINVAL;
+ }
+ verb = &uc_mgr->verb[verb_id];
+
+ /* we set the invalid verb at open() but we should still
+ * check that this succeeded */
+ if (verb == NULL)
+ return 0;
+
+ uc_dbg("verb %s", verb->name);
+ ret = exec_sequence(verb->disable, uc_mgr, list, handle);
+ if (ret < 0) {
+ uc_error("error: could not disable verb %s", verb->name);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int enable_use_case_device(snd_use_case_mgr_t *uc_mgr,
+ int device_id, snd_ctl_elem_list_t *list, snd_ctl_t *handle)
+{
+ struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb];
+ struct use_case_device *device = &verb->device[device_id];
+ int ret;
+
+ if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED)
+ return -EINVAL;
+
+ uc_dbg("device %s", device->name);
+ ret = exec_sequence(device->enable, uc_mgr, list, handle);
+ if (ret < 0) {
+ uc_error("error: could not enable device %s", device->name);
+ return ret;
+ }
+
+ set_device_status(uc_mgr, device_id, 1);
+ return 0;
+}
+
+static int disable_use_case_device(snd_use_case_mgr_t *uc_mgr,
+ int device_id, snd_ctl_elem_list_t *list, snd_ctl_t *handle)
+{
+ struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb];
+ struct use_case_device *device = &verb->device[device_id];
+ int ret;
+
+ if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED)
+ return -EINVAL;
+
+ uc_dbg("device %s", device->name);
+ ret = exec_sequence(device->disable, uc_mgr, list, handle);
+ if (ret < 0) {
+ uc_error("error: could not disable device %s", device->name);
+ return ret;
+ }
+
+ set_device_status(uc_mgr, device_id, 0);
+ return 0;
+}
+
+static int enable_use_case_modifier(snd_use_case_mgr_t *uc_mgr,
+ int modifier_id, snd_ctl_elem_list_t *list, snd_ctl_t *handle)
+{
+ struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb];
+ struct use_case_modifier *modifier = &verb->modifier[modifier_id];
+ int ret;
+
+ if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED)
+ return -EINVAL;
+
+ uc_dbg("modifier %s", modifier->name);
+ ret = exec_sequence(modifier->enable, uc_mgr, list, handle);
+ if (ret < 0) {
+ uc_error("error: could not enable modifier %s", modifier->name);
+ return ret;
+ }
+
+ set_modifier_status(uc_mgr, modifier_id, 1);
+ return 0;
+}
+
+static int disable_use_case_modifier(snd_use_case_mgr_t *uc_mgr,
+ int modifier_id, snd_ctl_elem_list_t *list, snd_ctl_t *handle)
+{
+ struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb];
+ struct use_case_modifier *modifier = &verb->modifier[modifier_id];
+ int ret;
+
+ if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED)
+ return -EINVAL;
+
+ uc_dbg("modifier %s", modifier->name);
+ ret = exec_sequence(modifier->disable, uc_mgr, list, handle);
+ if (ret < 0) {
+ uc_error("error: could not disable modifier %s", modifier->name);
+ return ret;
+ }
+
+ set_modifier_status(uc_mgr, modifier_id, 0);
+ return 0;
+}
+
+/*
+ * Tear down current use case verb, device and modifier.
+ */
+static int dismantle_use_case(snd_use_case_mgr_t *uc_mgr,
+ snd_ctl_elem_list_t *list, snd_ctl_t *handle)
+{
+ struct use_case_verb *verb = &uc_mgr->verb[uc_mgr->card.current_verb];
+ int ret, i;
+
+ /* No active verb */
+ if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED)
+ return 0;
+
+ /* disable all modifiers that are active */
+ for (i = 0; i < verb->num_modifiers; i++) {
+ if (get_modifier_status(uc_mgr,i)) {
+ ret = disable_use_case_modifier(uc_mgr, i, list, handle);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ /* disable all devices that are active */
+ for (i = 0; i < verb->num_devices; i++) {
+ if (get_device_status(uc_mgr,i)) {
+ ret = disable_use_case_device(uc_mgr, i, list, handle);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ /* disable verb */
+ ret = disable_use_case_verb(uc_mgr, uc_mgr->card.current_verb, list, handle);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+ /**
+ * \brief Dump sound card controls in format required for sequencer.
+ * \param card_name The name of the sound card to be dumped
+ * \return zero on success, otherwise a negative error code
+ */
+int snd_use_case_dump(const char *card_name)
+{
+ snd_ctl_t *handle;
+ snd_ctl_card_info_t *info;
+ snd_ctl_elem_list_t *list;
+ int ret, i, count, idx;
+ char ctl_name[8];
+
+ snd_ctl_card_info_alloca(&info);
+ snd_ctl_elem_list_alloca(&list);
+
+ idx = snd_card_get_index(card_name);
+ if (idx < 0)
+ return idx;
+ sprintf(ctl_name, "hw:%d", idx);
+
+ /* open and load snd card */
+ ret = snd_ctl_open(&handle, ctl_name, SND_CTL_READONLY);
+ if (ret < 0) {
+ uc_error("error: could not open controls for %s: %s",
+ card_name, snd_strerror(ret));
+ return ret;
+ }
+
+ ret = snd_ctl_card_info(handle, info);
+ if (ret < 0) {
+ uc_error("error: could not get control info for %s:%s",
+ card_name, snd_strerror(ret));
+ goto close;
+ }
+
+ ret = snd_ctl_elem_list(handle, list);
+ if (ret < 0) {
+ uc_error("error: cannot determine controls for %s: %s",
+ card_name, snd_strerror(ret));
+ goto close;
+ }
+
+ count = snd_ctl_elem_list_get_count(list);
+ if (count < 0) {
+ ret = 0;
+ goto close;
+ }
+
+ snd_ctl_elem_list_set_offset(list, 0);
+ if (snd_ctl_elem_list_alloc_space(list, count) < 0) {
+ uc_error("error: not enough memory for control elements");
+ ret = -ENOMEM;
+ goto close;
+ }
+ if ((ret = snd_ctl_elem_list(handle, list)) < 0) {
+ uc_error("error: cannot determine controls: %s",
+ snd_strerror(ret));
+ goto free;
+ }
+
+ /* iterate through each kcontrol and add to use
+ * case manager control list */
+ for (i = 0; i < count; ++i) {
+ snd_ctl_elem_id_t *id;
+ snd_ctl_elem_id_alloca(&id);
+ snd_ctl_elem_list_get_id(list, i, id);
+
+ /* dump to stdout in friendly format */
+ ret = dump_control(handle, id);
+ if (ret < 0) {
+ uc_error("error: control dump failed: %s",
+ snd_strerror(ret));
+ goto free;
+ }
+ }
+free:
+ snd_ctl_elem_list_free_space(list);
+close:
+ snd_ctl_close(handle);
+ return ret;
+}
+
+/**
+ * \brief List supported use case verbs for given soundcard
+ * \param uc_mgr use case manager
+ * \param verb returned list of supported use case verb id and names
+ * \return number of use case verbs if success, otherwise a negative error code
+ */
+int snd_use_case_get_verb_list(snd_use_case_mgr_t *uc_mgr,
+ const char **verb[])
+{
+ int ret;
+
+ pthread_mutex_lock(&uc_mgr->mutex);
+
+ *verb = uc_mgr->verb_list;
+ ret = uc_mgr->num_verbs;
+
+ pthread_mutex_unlock(&uc_mgr->mutex);
+ return ret;
+}
+
+/**
+ * \brief List supported use case devices for given verb
+ * \param uc_mgr use case manager
+ * \param verb verb id.
+ * \param device returned list of supported use case device id and names
+ * \return number of use case devices if success, otherwise a negative error code
+ */
+int snd_use_case_get_device_list(snd_use_case_mgr_t *uc_mgr,
+ const char *verb_name, const char **device[])
+{
+ struct use_case_verb *verb = NULL;
+ int i, ret = -EINVAL;
+
+ pthread_mutex_lock(&uc_mgr->mutex);
+
+ /* find verb name */
+ for (i = 0; i < uc_mgr->num_verbs; i++) {
+ verb = &uc_mgr->verb[i];
+ if (!strcmp(uc_mgr->verb[i].name, verb_name))
+ goto found;
+ }
+
+ uc_error("error: use case verb %s not found", verb_name);
+ goto out;
+
+found:
+ *device = verb->device_list;
+ ret = verb->num_devices;
+out:
+ pthread_mutex_unlock(&uc_mgr->mutex);
+ return ret;
+}
+
+/**
+ * \brief List supported use case verb modifiers for given verb
+ * \param uc_mgr use case manager
+ * \param verb verb id.
+ * \param mod returned list of supported use case modifier id and names
+ * \return number of use case modifiers if success, otherwise a negative error code
+ */
+int snd_use_case_get_mod_list(snd_use_case_mgr_t *uc_mgr,
+ const char *verb_name, const char **mod[])
+{
+ struct use_case_verb *verb = NULL;
+ int i, ret = -EINVAL;
+
+ pthread_mutex_lock(&uc_mgr->mutex);
+
+ /* find verb name */
+ for (i = 0; i <uc_mgr->num_verbs; i++) {
+ verb = &uc_mgr->verb[i];
+ if (!strcmp(uc_mgr->verb[i].name, verb_name))
+ goto found;
+ }
+
+ uc_error("error: use case verb %s not found", verb_name);
+ goto out;
+
+found:
+ *mod = verb->modifier_list;
+ ret = verb->num_modifiers;
+out:
+ pthread_mutex_unlock(&uc_mgr->mutex);
+ return ret;
+}
+
+static struct sequence_element *get_transition_sequence(
+ struct transition_sequence *trans_list, const char *name)
+{
+ struct transition_sequence *trans = trans_list;
+
+ while (trans) {
+ if (trans->name && !strcmp(trans->name, name))
+ return trans->transition;
+
+ trans = trans->next;
+ }
+
+ return NULL;
+}
+
+static int exec_transition_sequence(snd_use_case_mgr_t *uc_mgr,
+ struct sequence_element *trans_sequence)
+{
+ int ret;
+
+ ret = exec_sequence(trans_sequence, uc_mgr, uc_mgr->list,
+ uc_mgr->handle);
+ if (ret < 0)
+ uc_error("error: could not exec transition sequence");
+
+ return ret;
+}
+
+static int handle_transition_verb(snd_use_case_mgr_t *uc_mgr,
+ int new_verb_id)
+{
+ struct use_case_verb *old_verb = &uc_mgr->verb[uc_mgr->card.current_verb];
+ struct use_case_verb *new_verb;
+ static struct sequence_element *trans_sequence;
+
+ if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED)
+ return -EINVAL;
+
+ if (new_verb_id >= uc_mgr->num_verbs) {
+ uc_error("error: invalid new_verb id %d", new_verb_id);
+ return -EINVAL;
+ }
+
+ new_verb = &uc_mgr->verb[new_verb_id];
+
+ uc_dbg("new verb %s", new_verb->name);
+
+ trans_sequence = get_transition_sequence(old_verb->transition_list,
+ new_verb->name);
+ if (trans_sequence != NULL) {
+ int ret, i;
+
+ uc_dbg("find transition sequence %s->%s",
+ old_verb->name, new_verb->name);
+
+ /* disable all modifiers that are active */
+ for (i = 0; i < old_verb->num_modifiers; i++) {
+ if (get_modifier_status(uc_mgr,i)) {
+ ret = disable_use_case_modifier(uc_mgr, i,
+ uc_mgr->list, uc_mgr->handle);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ /* disable all devices that are active */
+ for (i = 0; i < old_verb->num_devices; i++) {
+ if (get_device_status(uc_mgr,i)) {
+ ret = disable_use_case_device(uc_mgr, i,
+ uc_mgr->list, uc_mgr->handle);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ ret = exec_transition_sequence(uc_mgr, trans_sequence);
+ if (ret)
+ return ret;
+
+ uc_mgr->card.current_verb = new_verb_id;
+
+ return 0;
+ }
+
+ return-EINVAL;
+}
+
+/**
+ * \brief Set new use case verb for sound card
+ * \param uc_mgr use case manager
+ * \param verb verb id
+ * \return zero if success, otherwise a negative error code
+ */
+int snd_use_case_set_verb(snd_use_case_mgr_t *uc_mgr,
+ const char *verb_name)
+{
+ int i = 0, ret = -EINVAL, inactive = 0;
+
+ pthread_mutex_lock(&uc_mgr->mutex);
+
+ uc_dbg("uc_mgr %p, verb_name %s", uc_mgr, verb_name);
+
+ /* check for "Inactive" */
+ if (!strcmp(verb_name, SND_USE_CASE_VERB_INACTIVE)) {
+ inactive = 1;
+ goto found;
+ }
+
+ /* find verb name */
+ for (i = 0; i <uc_mgr->num_verbs; i++) {
+ if (!strcmp(uc_mgr->verb[i].name, verb_name))
+ goto found;
+ }
+
+ uc_error("error: use case verb %s not found", verb_name);
+ goto out;
+found:
+ /* use case verb found - check that we actually changing the verb */
+ if (i == uc_mgr->card.current_verb) {
+ uc_dbg("current verb ID %d", i);
+ ret = 0;
+ goto out;
+ }
+
+ if (handle_transition_verb(uc_mgr, i) == 0)
+ goto out;
+
+ /*
+ * Dismantle the old use cases by running it's verb, device and modifier
+ * disable sequences
+ */
+ ret = dismantle_use_case(uc_mgr, uc_mgr->list, uc_mgr->handle);
+ if (ret < 0) {
+ uc_error("error: failed to dismantle current use case: %s",
+ uc_mgr->verb[i].name);
+ goto out;
+ }
+
+ /* we don't need to initialise new verb if inactive */
+ if (inactive)
+ goto out;
+
+ /* Initialise the new use case verb */
+ ret = enable_use_case_verb(uc_mgr, i, uc_mgr->list, uc_mgr->handle);
+ if (ret < 0)
+ uc_error("error: failed to initialise new use case: %s",
+ verb_name);
+out:
+ pthread_mutex_unlock(&uc_mgr->mutex);
+
+ return ret;
+}
+
+static int config_use_case_device(snd_use_case_mgr_t *uc_mgr,
+ const char *device_name, int enable)
+{
+ struct use_case_verb *verb;
+ int ret, i;
+
+ pthread_mutex_lock(&uc_mgr->mutex);
+
+ if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) {
+ uc_error("error: no valid use case verb set\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ verb = &uc_mgr->verb[uc_mgr->card.current_verb];
+
+ uc_dbg("current verb %s", verb->name);
+ uc_dbg("uc_mgr %p device_name %s", uc_mgr, device_name);
+
+ /* find device name and index */
+ for (i = 0; i <verb->num_devices; i++) {
+ uc_dbg("verb->num_devices %s", verb->device[i].name);
+ if (!strcmp(verb->device[i].name, device_name))
+ goto found;
+ }
+
+ uc_error("error: use case device %s not found", device_name);
+ ret = -EINVAL;
+ goto out;
+
+found:
+ if (enable) {
+ /* Initialise the new use case device */
+ ret = enable_use_case_device(uc_mgr, i, uc_mgr->list,
+ uc_mgr->handle);
+ if (ret < 0)
+ goto out;
+ } else {
+ /* disable the old device */
+ ret = disable_use_case_device(uc_mgr, i, uc_mgr->list,
+ uc_mgr->handle);
+ if (ret < 0)
+ goto out;
+ }
+
+out:
+ pthread_mutex_unlock(&uc_mgr->mutex);
+ return ret;
+}
+
+/**
+ * \brief Enable use case device
+ * \param uc_mgr Use case manager
+ * \param device the device to be enabled
+ * \return 0 = successful negative = error
+ */
+int snd_use_case_enable_device(snd_use_case_mgr_t *uc_mgr,
+ const char *device)
+{
+ return config_use_case_device(uc_mgr, device, 1);
+}
+
+/**
+ * \brief Disable use case device
+ * \param uc_mgr Use case manager
+ * \param device the device to be disabled
+ * \return 0 = successful negative = error
+ */
+int snd_use_case_disable_device(snd_use_case_mgr_t *uc_mgr,
+ const char *device)
+{
+ return config_use_case_device(uc_mgr, device, 0);
+}
+
+static struct use_case_device *get_device(snd_use_case_mgr_t *uc_mgr,
+ const char *name, int *id)
+{
+ struct use_case_verb *verb;
+ int i;
+
+ if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED)
+ return NULL;
+
+ verb = &uc_mgr->verb[uc_mgr->card.current_verb];
+
+ uc_dbg("current verb %s", verb->name);
+
+ for (i = 0; i < verb->num_devices; i++) {
+ uc_dbg("device %s", verb->device[i].name);
+
+ if (!strcmp(verb->device[i].name, name)) {
+ if (id)
+ *id = i;
+ return &verb->device[i];
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * \brief Disable old_device and then enable new_device.
+ * If from_device is not enabled just return.
+ * Check transition sequence firstly.
+ * \param uc_mgr Use case manager
+ * \param old the device to be closed
+ * \param new the device to be opened
+ * \return 0 = successful negative = error
+ */
+int snd_use_case_switch_device(snd_use_case_mgr_t *uc_mgr,
+ const char *old, const char *new)
+{
+ static struct sequence_element *trans_sequence;
+ struct use_case_device *old_device;
+ struct use_case_device *new_device;
+ int ret = 0, old_id, new_id;
+
+ uc_dbg("old %s, new %s", old, new);
+
+ pthread_mutex_lock(&uc_mgr->mutex);
+
+ old_device = get_device(uc_mgr, old, &old_id);
+ if (!old_device) {
+ uc_error("error: device %s not found", old);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (!get_device_status(uc_mgr, old_id)) {
+ uc_error("error: device %s not enabled", old);
+ goto out;
+ }
+
+ new_device = get_device(uc_mgr, new, &new_id);
+ if (!new_device) {
+ uc_error("error: device %s not found", new);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ trans_sequence = get_transition_sequence(old_device->transition_list, new);
+ if (trans_sequence != NULL) {
+
+ uc_dbg("find transition sequece %s->%s", old, new);
+
+ ret = exec_transition_sequence(uc_mgr, trans_sequence);
+ if (ret)
+ goto out;
+
+ set_device_status(uc_mgr, old_id, 0);
+ set_device_status(uc_mgr, new_id, 1);
+ } else {
+ /* use lock in config_use_case_device */
+ pthread_mutex_unlock(&uc_mgr->mutex);
+
+ config_use_case_device(uc_mgr, old, 0);
+ config_use_case_device(uc_mgr, new, 1);
+
+ return 0;
+ }
+out:
+ pthread_mutex_unlock(&uc_mgr->mutex);
+ return ret;
+}
+
+/*
+ * Check to make sure that the modifier actually supports any of the
+ * active devices.
+ */
+static int is_modifier_valid(snd_use_case_mgr_t *uc_mgr,
+ struct use_case_verb *verb, struct use_case_modifier *modifier)
+{
+ struct dev_list *dev_list;
+ int dev;
+
+ /* check modifier list against each enabled device */
+ for (dev = 0; dev < verb->num_devices; dev++) {
+ if (!get_device_status(uc_mgr, dev))
+ continue;
+
+ dev_list = modifier->dev_list;
+ uc_dbg("checking device %s for %s", verb->device[dev].name,
+ dev_list->name ? dev_list->name : "");
+
+ while (dev_list) {
+ uc_dbg("device supports %s", dev_list->name);
+ if (!strcmp(dev_list->name, verb->device[dev].name))
+ return 1;
+ dev_list = dev_list->next;
+ }
+ }
+ return 0;
+}
+
+static int config_use_case_mod(snd_use_case_mgr_t *uc_mgr,
+ const char *modifier_name, int enable)
+{
+ struct use_case_verb *verb;
+ int ret, i;
+
+ pthread_mutex_lock(&uc_mgr->mutex);
+
+ if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ verb = &uc_mgr->verb[uc_mgr->card.current_verb];
+
+ uc_dbg("current verb %s", verb->name);
+ uc_dbg("uc_mgr %p modifier_name %s", uc_mgr, modifier_name);
+
+ /* find modifier name */
+ for (i = 0; i <verb->num_modifiers; i++) {
+ uc_dbg("verb->num_modifiers %d %s", i, verb->modifier[i].name);
+ if (!strcmp(verb->modifier[i].name, modifier_name) &&
+ is_modifier_valid(uc_mgr, verb, &verb->modifier[i]))
+ goto found;
+ }
+
+ uc_error("error: use case modifier %s not found or invalid",
+ modifier_name);
+ ret = -EINVAL;
+ goto out;
+
+found:
+ if (enable) {
+ /* Initialise the new use case device */
+ ret = enable_use_case_modifier(uc_mgr, i, uc_mgr->list,
+ uc_mgr->handle);
+ if (ret < 0)
+ goto out;
+ } else {
+ /* disable the old device */
+ ret = disable_use_case_modifier(uc_mgr, i, uc_mgr->list,
+ uc_mgr->handle);
+ if (ret < 0)
+ goto out;
+ }
+
+out:
+ pthread_mutex_unlock(&uc_mgr->mutex);
+ return ret;
+}
+
+/**
+ * \brief Enable use case modifier
+ * \param uc_mgr Use case manager
+ * \param modifier the modifier to be enabled
+ * \return 0 = successful negative = error
+ */
+int snd_use_case_enable_modifier(snd_use_case_mgr_t *uc_mgr,
+ const char *modifier)
+{
+ return config_use_case_mod(uc_mgr, modifier, 1);
+}
+
+/**
+ * \brief Disable use case modifier
+ * \param uc_mgr Use case manager
+ * \param modifier the modifier to be disabled
+ * \return 0 = successful negative = error
+ */
+int snd_use_case_disable_modifier(snd_use_case_mgr_t *uc_mgr,
+ const char *modifier)
+{
+ return config_use_case_mod(uc_mgr, modifier, 0);
+}
+
+static struct use_case_modifier *get_modifier(snd_use_case_mgr_t *uc_mgr,
+ const char *name, int *mod_id)
+{
+ struct use_case_verb *verb;
+ int i;
+
+ if (uc_mgr->card.current_verb == VERB_NOT_INITIALISED)
+ return NULL;
+
+ verb = &uc_mgr->verb[uc_mgr->card.current_verb];
+
+ uc_dbg("current verb %s", verb->name);
+
+ uc_dbg("uc_mgr %p modifier_name %s", uc_mgr, name);
+
+ for (i = 0; i < verb->num_modifiers; i++) {
+ uc_dbg("verb->num_devices %s", verb->modifier[i].name);
+
+ if (!strcmp(verb->modifier[i].name, name)) {
+ if (mod_id)
+ *mod_id = i;
+ return &verb->modifier[i];
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * \brief Disable old_modifier and then enable new_modifier.
+ * If old_modifier is not enabled just return.
+ * Check transition sequence firstly.
+ * \param uc_mgr Use case manager
+ * \param old the modifier to be closed
+ * \param new the modifier to be opened
+ * \return 0 = successful negative = error
+ */
+int snd_use_case_switch_modifier(snd_use_case_mgr_t *uc_mgr,
+ const char *old, const char *new)
+{
+ struct use_case_modifier *old_modifier;
+ struct use_case_modifier *new_modifier;
+ static struct sequence_element *trans_sequence;
+ int ret = 0, old_id, new_id
+
+ uc_dbg("old %s, new %s", old, new);
+
+ pthread_mutex_lock(&uc_mgr->mutex);
+
+ old_modifier = get_modifier(uc_mgr, old, &old_id);
+ if (!old_modifier) {
+ uc_error("error: modifier %s not found", old);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (!get_modifier_status(uc_mgr, old_id)) {
+ uc_error("error: modifier %s not enabled", old);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ new_modifier = get_modifier(uc_mgr, new, &new_id);
+ if (!new_modifier) {
+ uc_error("error: modifier %s not found", new);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ trans_sequence = get_transition_sequence(
+ old_modifier->transition_list, new);
+ if (trans_sequence != NULL) {
+ uc_dbg("find transition sequence %s->%s", old, new);
+
+ ret = exec_transition_sequence(uc_mgr, trans_sequence);
+ if (ret)
+ goto out;
+
+ set_device_status(uc_mgr, old_id, 0);
+ set_device_status(uc_mgr, new_id, 1);
+ } else {
+ /* use lock in config_use_case_mod*/
+ pthread_mutex_unlock(&uc_mgr->mutex);
+
+ config_use_case_mod(uc_mgr, old, 0);
+ config_use_case_mod(uc_mgr, new, 1);
+
+ return 0;
+ }
+out:
+ pthread_mutex_unlock(&uc_mgr->mutex);
+ return ret;
+}
+
+/**
+ * \brief Get current use case verb from sound card
+ * \param uc_mgr use case manager
+ * \return Verb Name if success, otherwise NULL
+ */
+const char *snd_use_case_get_verb(snd_use_case_mgr_t *uc_mgr)
+{
+ const char *ret = NULL;
+
+ pthread_mutex_lock(&uc_mgr->mutex);
+
+ if (uc_mgr->card.current_verb != VERB_NOT_INITIALISED)
+ ret = uc_mgr->verb_list[uc_mgr->card.current_verb];
+
+ pthread_mutex_unlock(&uc_mgr->mutex);
+
+ return ret;
+}
+
+/**
+ * \brief Get device status for current use case verb
+ * \param uc_mgr Use case manager
+ * \param device_name The device we are interested in.
+ * \return - 1 = enabled, 0 = disabled, negative = error
+ */
+int snd_use_case_get_device_status(snd_use_case_mgr_t *uc_mgr,
+ const char *device_name)
+{
+ struct use_case_device *device;
+ int ret = -EINVAL, dev_id;
+
+ pthread_mutex_lock(&uc_mgr->mutex);
+
+ device = get_device(uc_mgr, device_name, &dev_id);
+ if (device == NULL) {
+ uc_error("error: use case device %s not found", device_name);
+ goto out;
+ }
+
+ ret = get_device_status(uc_mgr, dev_id);
+out:
+ pthread_mutex_unlock(&uc_mgr->mutex);
+
+ return ret;
+}
+
+/**
+ * \brief Get modifier status for current use case verb
+ * \param uc_mgr Use case manager
+ * \param device_name The device we are interested in.
+ * \return - 1 = enabled, 0 = disabled, negative = error
+ */
+int snd_use_case_get_modifier_status(snd_use_case_mgr_t *uc_mgr,
+ const char *modifier_name)
+{
+ struct use_case_modifier *modifier;
+ int ret = -EINVAL, mod_id;
+
+ pthread_mutex_lock(&uc_mgr->mutex);
+
+ modifier = get_modifier(uc_mgr, modifier_name, &mod_id);
+ if (modifier == NULL) {
+ uc_error("error: use case modifier %s not found", modifier_name);
+ goto out;
+ }
+
+ ret = get_modifier_status(uc_mgr, mod_id);
+out:
+ pthread_mutex_unlock(&uc_mgr->mutex);
+
+ return ret;
+}
+
+/**
+ * \brief Get current use case verb QoS
+ * \param uc_mgr use case manager
+ * \return QoS level
+ */
+enum snd_use_case_qos
+ snd_use_case_get_verb_qos(snd_use_case_mgr_t *uc_mgr)
+{
+ struct use_case_verb *verb;
+ enum snd_use_case_qos ret = SND_USE_CASE_QOS_UNKNOWN;
+
+ pthread_mutex_lock(&uc_mgr->mutex);
+
+ if (uc_mgr->card.current_verb != VERB_NOT_INITIALISED) {
+ verb = &uc_mgr->verb[uc_mgr->card.current_verb];
+ ret = verb->qos;
+ }
+
+ pthread_mutex_unlock(&uc_mgr->mutex);
+
+ return ret;
+}
+
+/**
+ * \brief Get current use case modifier QoS
+ * \param uc_mgr use case manager
+ * \return QoS level
+ */
+enum snd_use_case_qos
+ snd_use_case_get_mod_qos(snd_use_case_mgr_t *uc_mgr,
+ const char *modifier_name)
+{
+ struct use_case_modifier *modifier;
+ enum snd_use_case_qos ret = SND_USE_CASE_QOS_UNKNOWN;
+
+ pthread_mutex_lock(&uc_mgr->mutex);
+
+ modifier = get_modifier(uc_mgr, modifier_name, NULL);
+ if (modifier != NULL)
+ ret = modifier->qos;
+ else
+ uc_error("error: use case modifier %s not found", modifier_name);
+ pthread_mutex_unlock(&uc_mgr->mutex);
+
+ return ret;
+}
+
+/**
+ * \brief Get current use case verb playback PCM
+ * \param uc_mgr use case manager
+ * \return PCM number if success, otherwise negative
+ */
+int snd_use_case_get_verb_playback_pcm(snd_use_case_mgr_t *uc_mgr)
+{
+ struct use_case_verb *verb;
+ int ret = -EINVAL;
+
+ pthread_mutex_lock(&uc_mgr->mutex);
+
+ if (uc_mgr->card.current_verb != VERB_NOT_INITIALISED) {
+ verb = &uc_mgr->verb[uc_mgr->card.current_verb];
+ ret = verb->playback_pcm;
+ }
+
+ pthread_mutex_unlock(&uc_mgr->mutex);
+
+ return ret;
+}
+
+/**
+ * \brief Get current use case verb playback PCM
+ * \param uc_mgr use case manager
+ * \return PCM number if success, otherwise negative
+ */
+int snd_use_case_get_verb_capture_pcm(snd_use_case_mgr_t *uc_mgr)
+{
+ struct use_case_verb *verb;
+ int ret = -EINVAL;
+
+ pthread_mutex_lock(&uc_mgr->mutex);
+
+ if (uc_mgr->card.current_verb != VERB_NOT_INITIALISED) {
+ verb = &uc_mgr->verb[uc_mgr->card.current_verb];
+ ret = verb->capture_pcm;
+ }
+
+ pthread_mutex_unlock(&uc_mgr->mutex);
+
+ return ret;
+}
+
+/**
+ * \brief Get current use case modifier playback PCM
+ * \param uc_mgr use case manager
+ * \return PCM number if success, otherwise negative
+ */
+int snd_use_case_get_mod_playback_pcm(snd_use_case_mgr_t *uc_mgr,
+ const char *modifier_name)
+{
+ struct use_case_modifier *modifier;
+ int ret = -EINVAL;
+
+ pthread_mutex_lock(&uc_mgr->mutex);
+
+ modifier = get_modifier(uc_mgr, modifier_name, NULL);
+ if (modifier == NULL)
+ uc_error("error: use case modifier %s not found",
+ modifier_name);
+ else
+ ret = modifier->playback_pcm;
+
+ pthread_mutex_unlock(&uc_mgr->mutex);
+
+ return ret;
+}
+
+/**
+ * \brief Get current use case modifier playback PCM
+ * \param uc_mgr use case manager
+ * \return PCM number if success, otherwise negative
+ */
+int snd_use_case_get_mod_capture_pcm(snd_use_case_mgr_t *uc_mgr,
+ const char *modifier_name)
+{
+ struct use_case_modifier *modifier;
+ int ret = -EINVAL;
+
+ pthread_mutex_lock(&uc_mgr->mutex);
+
+ modifier = get_modifier(uc_mgr, modifier_name, NULL);
+ if (modifier == NULL)
+ uc_error("error: use case modifier %s not found",
+ modifier_name);
+ else
+ ret = modifier->capture_pcm;
+
+ pthread_mutex_unlock(&uc_mgr->mutex);
+
+ return ret;
+}
+
+/**
+ * \brief Get volume/mute control name depending on use case device.
+ * \param uc_mgr use case manager
+ * \param type the control type we are looking for
+ * \param device_name The use case device we are interested in.
+ * \return control name if success, otherwise NULL
+ *
+ * Get the control id for common volume and mute controls that are aliased
+ * in the named use case device.
+ */
+const char *snd_use_case_get_device_ctl_elem_name(snd_use_case_mgr_t *uc_mgr,
+ enum snd_use_case_control_alias type, const char *device_name)
+{
+ struct use_case_device *device;
+ const char *kcontrol_name = NULL;
+
+ pthread_mutex_lock(&uc_mgr->mutex);
+
+ device = get_device(uc_mgr, device_name, NULL);
+ if (!device) {
+ uc_error("error: device %s not found", device_name);
+ goto out;
+ }
+
+ switch (type) {
+ case SND_USE_CASE_ALIAS_PLAYBACK_VOLUME:
+ kcontrol_name = device->playback_volume_id;
+ break;
+ case SND_USE_CASE_ALIAS_CAPTURE_VOLUME:
+ kcontrol_name = device->capture_volume_id;
+ break;
+ case SND_USE_CASE_ALIAS_PLAYBACK_SWITCH:
+ kcontrol_name = device->playback_switch_id;
+ break;
+ case SND_USE_CASE_ALIAS_CAPTURE_SWITCH:
+ kcontrol_name = device->capture_switch_id;
+ break;
+ default:
+ uc_error("error: invalid control alias %d", type);
+ break;
+ }
+
+out:
+ pthread_mutex_unlock(&uc_mgr->mutex);
+
+ return kcontrol_name;
+}
+
+/**
+ * \brief Get volume/mute control IDs depending on use case modifier.
+ * \param uc_mgr use case manager
+ * \param type the control type we are looking for
+ * \param modifier_name The use case modifier we are interested in.
+ * \return ID if success, otherwise a negative error code
+ *
+ * Get the control id for common volume and mute controls that are aliased
+ * in the named use case device.
+ */
+const char *snd_use_case_get_modifier_ctl_elem_name(snd_use_case_mgr_t *uc_mgr,
+ enum snd_use_case_control_alias type, const char *modifier_name)
+{
+ struct use_case_modifier *modifier;
+ const char *kcontrol_name = NULL;
+
+ pthread_mutex_lock(&uc_mgr->mutex);
+
+ modifier = get_modifier(uc_mgr, modifier_name, NULL);
+ if (!modifier) {
+ uc_error("error: modifier %s not found", modifier_name);
+ goto out;
+ }
+
+ switch (type) {
+ case SND_USE_CASE_ALIAS_PLAYBACK_VOLUME:
+ kcontrol_name = modifier->playback_volume_id;
+ break;
+ case SND_USE_CASE_ALIAS_CAPTURE_VOLUME:
+ kcontrol_name = modifier->capture_volume_id;
+ break;
+ case SND_USE_CASE_ALIAS_PLAYBACK_SWITCH:
+ kcontrol_name = modifier->playback_switch_id;
+ break;
+ case SND_USE_CASE_ALIAS_CAPTURE_SWITCH:
+ kcontrol_name = modifier->capture_switch_id;
+ break;
+ default:
+ uc_error("error: invalid control alias %d", type);
+ break;
+ }
+
+out:
+ pthread_mutex_unlock(&uc_mgr->mutex);
+
+ return kcontrol_name;
+}