alsa-lib/src/ucm/main.c

1449 lines
35 KiB
C
Raw Normal View History

/*
* 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.
* Copyright (C) 2010 Red Hat Inc.
* Authors: Liam Girdwood <lrg@slimlogic.co.uk>
* Stefan Schmidt <stefan@slimlogic.co.uk>
* Justin Xu <justinx@slimlogic.co.uk>
* Jaroslav Kysela <perex@perex.cz>
*/
#include "ucm_local.h"
#include <pthread.h>
/**
* \brief Execute the sequence
* \param uc_mgr Use case manager
* \param seq Sequence
* \return zero on success, otherwise a negative error code
*/
static int execute_sequence(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED,
struct list_head *seq)
{
struct list_head *pos;
struct sequence_element *s;
list_for_each(pos, seq) {
s = list_entry(pos, struct sequence_element, list);
switch (s->type) {
case SEQUENCE_ELEMENT_TYPE_CSET:
uc_error("cset not yet implemented: '%s'", s->data.cset);
break;
case SEQUENCE_ELEMENT_TYPE_SLEEP:
usleep(s->data.sleep);
break;
case SEQUENCE_ELEMENT_TYPE_EXEC:
uc_error("exec not yet implemented: '%s'", s->data.exec);
break;
default:
uc_error("unknown sequence command %i", s->type);
break;
}
}
return 0;
}
/**
* \brief Import master config and execute the default sequence
* \param uc_mgr Use case manager
* \return zero on success, otherwise a negative error code
*/
static int import_master_config(snd_use_case_mgr_t *uc_mgr)
{
int err;
err = uc_mgr_import_master_config(uc_mgr);
if (err < 0)
return err;
err = execute_sequence(uc_mgr, &uc_mgr->default_list);
if (err < 0)
uc_error("Unable to execute default sequence");
return err;
}
/**
* \brief Universal find - string in a list
* \param uc_mgr Use case manager
* \param list List of structures
* \param offset Offset of list structure
* \param soffset Offset of string structure
* \param match String to match
* \return structure on success, otherwise a NULL (not found)
*/
static void *find0(snd_use_case_mgr_t *uc_mgr ATTRIBUTE_UNUSED,
struct list_head *list,
unsigned long offset,
unsigned long soffset,
const char *match)
{
struct list_head *pos;
char *ptr, *str;
list_for_each(pos, list) {
ptr = list_entry_offset(pos, char, offset);
str = *((char **)(ptr + soffset));
if (strcmp(str, match) == 0)
return ptr;
}
return NULL;
}
#define find(uc_mgr, list, type, member, value, match) \
find0(uc_mgr, list, \
(unsigned long)(&((type *)0)->member), \
(unsigned long)(&((type *)0)->value), match)
/**
* \brief Find verb
* \param uc_mgr Use case manager
* \param verb_name verb to find
* \return structure on success, otherwise a NULL (not found)
*/
static inline struct use_case_verb *find_verb(snd_use_case_mgr_t *uc_mgr,
const char *_name)
{
return find(uc_mgr, &uc_mgr->verb_list,
struct use_case_verb, list, name,
_name);
}
/**
* \brief Set verb
* \param uc_mgr Use case manager
* \param verb verb to set
* \return zero on success, otherwise a negative error code
*/
static int set_verb(snd_use_case_mgr_t *uc_mgr,
struct use_case_verb *verb,
int enable)
{
struct list_head *seq;
int err;
if (enable) {
seq = &verb->enable_list;
} else {
seq = &verb->disable_list;
}
err = execute_sequence(uc_mgr, seq);
if (enable && err >= 0)
uc_mgr->active_verb = verb;
return err;
}
/**
* \brief Init sound card use case manager.
* \param uc_mgr Returned use case manager pointer
* \param card_name name of card to open
* \return zero on success, otherwise a negative error code
*/
int snd_use_case_mgr_open(snd_use_case_mgr_t **mgr,
const char *card_name)
{
snd_use_case_mgr_t *uc_mgr;
int err;
/* create a new UCM */
uc_mgr = calloc(1, sizeof(snd_use_case_mgr_t));
if (uc_mgr == NULL)
return -ENOMEM;
INIT_LIST_HEAD(&uc_mgr->verb_list);
INIT_LIST_HEAD(&uc_mgr->default_list);
pthread_mutex_init(&uc_mgr->mutex, NULL);
uc_mgr->card_name = strdup(card_name);
if (uc_mgr->card_name == NULL) {
free(uc_mgr);
return -ENOMEM;
}
/* get info on use_cases and verify against card */
err = import_master_config(uc_mgr);
if (err < 0) {
uc_error("error: failed to import %s use case configuration %d",
card_name, err);
goto err;
}
*mgr = uc_mgr;
return 0;
err:
uc_mgr_free(uc_mgr);
return err;
}
/**
* \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 err;
pthread_mutex_lock(&uc_mgr->mutex);
uc_mgr_free_verb(uc_mgr);
/* reload all use cases */
err = import_master_config(uc_mgr);
if (err < 0) {
uc_error("error: failed to reload use cases\n");
pthread_mutex_unlock(&uc_mgr->mutex);
return -EINVAL;
}
pthread_mutex_unlock(&uc_mgr->mutex);
return err;
}
/**
* \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)
{
uc_mgr_free(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)
{
struct list_head *pos, *npos;
struct use_case_modifier *modifier;
struct use_case_device *device;
int err;
pthread_mutex_lock(&uc_mgr->mutex);
list_for_each_safe(pos, npos, &uc_mgr->active_modifiers) {
modifier = list_entry(pos, struct use_case_modifier,
active_list);
err = disable_modifier(uc_mgr, modifier);
if (err < 0)
uc_error("Unable to disable modifier %s", modifier->name);
}
INIT_LIST_HEAD(&uc_mgr->active_modifiers);
list_for_each_safe(pos, npos, &uc_mgr->active_devices) {
device = list_entry(pos, struct use_case_device,
active_list);
err = disable_device(uc_mgr, device);
if (err < 0)
uc_error("Unable to disable device %s", device->name);
}
INIT_LIST_HEAD(&uc_mgr->active_devices);
err = disable_verb(uc_mgr, uc_mgr->active_verb);
if (err < 0) {
uc_error("Unable to disable verb %s", uc_mgr->active_verb->name);
return err;
}
uc_mgr->active_verb = NULL;
err = execute_sequence(uc_mgr, &uc_mgr->default_list);
pthread_mutex_unlock(&uc_mgr->mutex);
return err;
}
#if 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;
}
#endif