satip-axe/kernel/sound/stm/conv.c
2015-03-26 17:24:57 +01:00

1124 lines
26 KiB
C

/*
* STMicroelectronics System-on-Chips' generic converters infrastructure
*
* Copyright (c) 2005-2007 STMicroelectronics Limited
*
* Author: Pawel Moll <pawel.moll@st.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU 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
*
*/
#include <linux/init.h>
#include <linux/list.h>
#include <linux/device.h>
#include <sound/control.h>
#include "common.h"
extern int snd_stm_debug_level;
/*
* Converters infrastructure description
*/
struct snd_stm_conv_group;
struct snd_stm_conv_source;
struct snd_stm_conv_converter {
struct list_head list;
struct snd_stm_conv_group *group;
struct snd_stm_conv_ops *ops;
void *priv;
int source_channel_from;
int source_channel_to;
int enabled;
int muted_by_source;
int muted_by_user;
spinlock_t status_lock; /* Protects enabled and muted_by_* */
struct snd_kcontrol *ctl_mute;
snd_stm_magic_field;
};
struct snd_stm_conv_group {
struct list_head list;
struct snd_stm_conv_source *source;
struct list_head converters;
int enabled;
int muted_by_source;
snd_stm_magic_field;
char name[1]; /* "Expandable" */
};
#define SND_STM_BUS_ID_SIZE 30
struct snd_stm_conv_source {
struct list_head list;
struct bus_type *bus;
char bus_id[SND_STM_BUS_ID_SIZE];
int channels_num;
struct snd_card *card;
int card_device;
struct snd_stm_conv_group *group_selected;
struct snd_stm_conv_group *group_active;
struct list_head groups;
struct snd_kcontrol *ctl_route;
snd_stm_magic_field;
};
static LIST_HEAD(snd_stm_conv_sources); /* Sources list */
static DEFINE_MUTEX(snd_stm_conv_mutex); /* Big Converters Structure Lock ;-) */
/*
* Converter control interface implementation
*/
const char *snd_stm_conv_get_name(struct snd_stm_conv_group *group)
{
snd_stm_printd(1, "snd_stm_conv_get_name(group=%p)\n", group);
BUG_ON(!group);
BUG_ON(!snd_stm_magic_valid(group));
return group->name;
}
unsigned int snd_stm_conv_get_format(struct snd_stm_conv_group *group)
{
unsigned int result = 0;
struct snd_stm_conv_converter *converter;
snd_stm_printd(1, "snd_stm_conv_get_format(group=%p)\n", group);
BUG_ON(!group);
BUG_ON(!snd_stm_magic_valid(group));
/* All configured converters must share the same input format -
* get first of them and check the rest; if any of converters
* has different opinion than others - raise an error! */
list_for_each_entry(converter, &group->converters, list) {
unsigned int format = converter->ops->get_format(
converter->priv);
if (result == 0) {
result = format;
} else if (format != result) {
snd_stm_printe("Wrong format (0x%x) for converter %p"
" (the rest says 0x%x)!\n", format,
converter, result);
snd_BUG();
}
}
return result;
}
int snd_stm_conv_get_oversampling(struct snd_stm_conv_group *group)
{
int result = 0;
struct snd_stm_conv_converter *converter;
snd_stm_printd(1, "snd_stm_conv_get_oversampling(group=%p)\n",
group);
BUG_ON(!group);
BUG_ON(!snd_stm_magic_valid(group));
/* All configured converters must share the same oversampling value -
* get first of them and check the rest; if any of converters
* has different opinion than others - raise an error! */
list_for_each_entry(converter, &group->converters, list) {
int oversampling;
BUG_ON(!converter);
BUG_ON(!snd_stm_magic_valid(converter));
oversampling = converter->ops->get_oversampling(
converter->priv);
if (result == 0) {
result = oversampling;
} else if (oversampling != result) {
snd_stm_printe("Wrong oversampling value (%d) for "
"converter %p (the rest says %d)!\n",
oversampling, converter, result);
snd_BUG();
}
}
return result;
}
int snd_stm_conv_enable(struct snd_stm_conv_group *group,
int channel_from, int channel_to)
{
int result = 0;
struct snd_stm_conv_converter *converter;
snd_stm_printd(1, "snd_stm_conv_enable(group=%p, channel_from=%d, "
"channel_to=%d)\n", group, channel_from, channel_to);
BUG_ON(channel_to < channel_from);
BUG_ON(!group);
BUG_ON(!snd_stm_magic_valid(group));
BUG_ON(group->enabled);
group->enabled = 1;
list_for_each_entry(converter, &group->converters, list) {
BUG_ON(!converter);
BUG_ON(!snd_stm_magic_valid(converter));
BUG_ON(converter->enabled);
spin_lock(&converter->status_lock);
if ((channel_from <= converter->source_channel_from &&
converter->source_channel_from <= channel_to) ||
(channel_from <= converter->source_channel_to &&
converter->source_channel_to <= channel_to)) {
converter->enabled = 1;
if (converter->ops->set_enabled) {
int done = converter->ops->set_enabled(1,
converter->priv);
if (done != 0) {
snd_stm_printe("Failed to enable "
"converter %p!\n",
converter);
result = done;
}
}
}
spin_unlock(&converter->status_lock);
}
return result;
}
int snd_stm_conv_disable(struct snd_stm_conv_group *group)
{
int result = 0;
struct snd_stm_conv_converter *converter;
snd_stm_printd(1, "snd_stm_conv_disable(group=%p)\n", group);
BUG_ON(!group);
BUG_ON(!snd_stm_magic_valid(group));
BUG_ON(!group->enabled);
group->enabled = 0;
list_for_each_entry(converter, &group->converters, list) {
BUG_ON(!converter);
BUG_ON(!snd_stm_magic_valid(converter));
spin_lock(&converter->status_lock);
if (converter->enabled) {
converter->enabled = 0;
if (converter->ops->set_enabled) {
int done = converter->ops->set_enabled(0,
converter->priv);
if (done != 0) {
snd_stm_printe("Failed to disable "
"converter %p!\n",
converter);
result = done;
}
}
}
spin_unlock(&converter->status_lock);
}
return result;
}
int snd_stm_conv_mute(struct snd_stm_conv_group *group)
{
int result = 0;
struct snd_stm_conv_converter *converter;
snd_stm_printd(1, "snd_stm_conv_mute(group=%p)\n", group);
BUG_ON(!group);
BUG_ON(!snd_stm_magic_valid(group));
BUG_ON(!group->enabled);
BUG_ON(group->muted_by_source);
group->muted_by_source = 1;
list_for_each_entry(converter, &group->converters, list) {
BUG_ON(!converter);
BUG_ON(!snd_stm_magic_valid(converter));
spin_lock(&converter->status_lock);
if (converter->enabled) {
converter->muted_by_source = 1;
if (converter->ops->set_muted &&
!converter->muted_by_user) {
int done = converter->ops->set_muted(1,
converter->priv);
if (done != 0) {
snd_stm_printe("Failed to mute "
"converter %p!\n",
converter);
result = done;
}
}
}
spin_unlock(&converter->status_lock);
}
return result;
}
int snd_stm_conv_unmute(struct snd_stm_conv_group *group)
{
int result = 0;
struct snd_stm_conv_converter *converter;
snd_stm_printd(1, "snd_stm_conv_unmute(group=%p)\n", group);
BUG_ON(!group);
BUG_ON(!snd_stm_magic_valid(group));
BUG_ON(!group->enabled);
BUG_ON(!group->muted_by_source);
group->muted_by_source = 0;
list_for_each_entry(converter, &group->converters, list) {
BUG_ON(!converter);
BUG_ON(!snd_stm_magic_valid(converter));
spin_lock(&converter->status_lock);
if (converter->enabled) {
converter->muted_by_source = 0;
if (converter->ops->set_muted &&
!converter->muted_by_user) {
int done = converter->ops->set_muted(0,
converter->priv);
if (done != 0) {
snd_stm_printe("Failed to unmute "
"converter %p!\n",
converter);
result = done;
}
}
}
spin_unlock(&converter->status_lock);
}
return result;
}
/*
* ALSA controls
*/
static int snd_stm_conv_ctl_mute_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_stm_conv_converter *converter = snd_kcontrol_chip(kcontrol);
snd_stm_printd(1, "snd_stm_conv_ctl_mute_get(kcontrol=0x%p,"
" ucontrol=0x%p)\n", kcontrol, ucontrol);
BUG_ON(!converter);
BUG_ON(!snd_stm_magic_valid(converter));
spin_lock(&converter->status_lock);
ucontrol->value.integer.value[0] = !converter->muted_by_user;
spin_unlock(&converter->status_lock);
return 0;
}
static int snd_stm_conv_ctl_mute_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_stm_conv_converter *converter = snd_kcontrol_chip(kcontrol);
int changed = 0;
snd_stm_printd(1, "snd_stm_conv_ctl_mute_put(kcontrol=0x%p,"
" ucontrol=0x%p)\n", kcontrol, ucontrol);
BUG_ON(!converter);
BUG_ON(!snd_stm_magic_valid(converter));
BUG_ON(!converter->ops->set_muted);
spin_lock(&converter->status_lock);
if (ucontrol->value.integer.value[0] !=
!converter->muted_by_user) {
changed = 1;
converter->muted_by_user =
!ucontrol->value.integer.value[0];
if (converter->enabled &&
converter->muted_by_user &&
!converter->muted_by_source)
converter->ops->set_muted(1, converter->priv);
else if (converter->enabled &&
!converter->muted_by_user &&
!converter->muted_by_source)
converter->ops->set_muted(0, converter->priv);
}
spin_unlock(&converter->status_lock);
return changed;
}
static struct snd_kcontrol_new snd_stm_conv_ctl_mute = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Master Playback Switch",
.info = snd_stm_ctl_boolean_info,
.get = snd_stm_conv_ctl_mute_get,
.put = snd_stm_conv_ctl_mute_put,
};
static int snd_stm_conv_ctl_mute_add(struct snd_stm_conv_converter *converter)
{
int result;
struct snd_stm_conv_source *source;
struct snd_kcontrol *ctl_mute;
snd_stm_printd(1, "snd_stm_conv_ctl_mute_add(converter=%p)\n",
converter);
BUG_ON(!converter);
BUG_ON(!snd_stm_magic_valid(converter));
source = converter->group->source;
BUG_ON(!source);
BUG_ON(!snd_stm_magic_valid(source));
snd_stm_conv_ctl_mute.device = source->card_device;
ctl_mute = snd_ctl_new1(&snd_stm_conv_ctl_mute, converter);
result = snd_ctl_add(source->card, ctl_mute);
if (result >= 0) {
/* We will have to manually dispose "hot-plugged" controls...
* ("normal" ones will be disposed during snd_card_free) */
if (snd_stm_card_is_registered())
converter->ctl_mute = ctl_mute;
snd_stm_conv_ctl_mute.index++;
} else {
snd_stm_printe("Error %d while adding mute ALSA control!\n",
result);
}
return result;
}
static int snd_stm_conv_ctl_route_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
struct snd_stm_conv_source *source = snd_kcontrol_chip(kcontrol);
struct snd_stm_conv_group *group;
int item = 0;
snd_stm_printd(1, "snd_stm_conv_ctl_route_info(kcontrol=0x%p,"
" uinfo=0x%p)\n", kcontrol, uinfo);
BUG_ON(!source);
BUG_ON(!snd_stm_magic_valid(source));
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
mutex_lock(&snd_stm_conv_mutex);
list_for_each_entry(group, &source->groups, list) {
if (list_is_last(&group->list, &source->groups) &&
uinfo->value.enumerated.item > item)
uinfo->value.enumerated.item = item;
if (item == uinfo->value.enumerated.item)
snprintf(uinfo->value.enumerated.name, 64, "%s",
group->name);
item++;
};
uinfo->value.enumerated.items = item;
mutex_unlock(&snd_stm_conv_mutex);
return 0;
}
static int snd_stm_conv_ctl_route_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_stm_conv_source *source = snd_kcontrol_chip(kcontrol);
struct snd_stm_conv_group *group;
int item = 0;
snd_stm_printd(1, "snd_stm_conv_ctl_route_get(kcontrol=0x%p,"
" ucontrol=0x%p)\n", kcontrol, ucontrol);
BUG_ON(!source);
BUG_ON(!snd_stm_magic_valid(source));
mutex_lock(&snd_stm_conv_mutex);
ucontrol->value.enumerated.item[0] = 0; /* First is default ;-) */
list_for_each_entry(group, &source->groups, list) {
if (group == source->group_selected) {
ucontrol->value.enumerated.item[0] = item;
break;
}
item++;
};
mutex_unlock(&snd_stm_conv_mutex);
return 0;
}
static int snd_stm_conv_ctl_route_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
int changed = 0;
struct snd_stm_conv_source *source = snd_kcontrol_chip(kcontrol);
struct snd_stm_conv_group *group;
int item = 0;
snd_stm_printd(1, "snd_stm_conv_ctl_route_put(kcontrol=0x%p,"
" ucontrol=0x%p)\n", kcontrol, ucontrol);
BUG_ON(!source);
BUG_ON(!snd_stm_magic_valid(source));
mutex_lock(&snd_stm_conv_mutex);
list_for_each_entry(group, &source->groups, list) {
if (item == ucontrol->value.enumerated.item[0]) {
if (group != source->group_selected) {
changed = 1;
source->group_selected = group;
}
break;
}
item++;
}
mutex_unlock(&snd_stm_conv_mutex);
return changed;
}
static struct snd_kcontrol_new snd_stm_conv_ctl_route = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "PCM Playback Route",
.info = snd_stm_conv_ctl_route_info,
.get = snd_stm_conv_ctl_route_get,
.put = snd_stm_conv_ctl_route_put,
};
static int snd_stm_conv_ctl_route_add(struct snd_stm_conv_source *source)
{
struct snd_kcontrol *ctl_route;
int result;
snd_stm_printd(1, "snd_stm_conv_ctl_route_add(source=%p)\n", source);
BUG_ON(!source);
BUG_ON(!snd_stm_magic_valid(source));
snd_stm_conv_ctl_route.device = source->card_device;
ctl_route = snd_ctl_new1(&snd_stm_conv_ctl_route, source);
result = snd_ctl_add(source->card, ctl_route);
if (result >= 0) {
/* We will have to manually dispose "hot-plugged"
* controls... ("normal" ones will be disposed
* during snd_card_free) */
if (snd_stm_card_is_registered())
source->ctl_route = ctl_route;
snd_stm_conv_ctl_route.index++;
} else {
snd_stm_printe("Error %d while adding route ALSA "
"control!\n", result);
}
return result;
}
/*
* Converters router implementation
*/
static inline int snd_stm_conv_more_than_one_entry(const struct list_head *head)
{
return !list_empty(head) && !list_is_last(head->next, head);
}
static struct snd_stm_conv_source *snd_stm_conv_get_source(
struct bus_type *bus, const char *bus_id)
{
struct snd_stm_conv_source *source;
snd_stm_printd(1, "snd_stm_conv_get_source(bus=%p, bus_id='%s')\n",
bus, bus_id);
BUG_ON(!bus);
BUG_ON(!bus_id);
mutex_lock(&snd_stm_conv_mutex);
list_for_each_entry(source, &snd_stm_conv_sources, list)
if (bus == source->bus && strcmp(bus_id, source->bus_id) == 0)
goto done; /* Already known source */
/* First time see... */
source = kzalloc(sizeof(*source), GFP_KERNEL);
if (!source) {
snd_stm_printe("Can't allocate memory for source!\n");
goto done;
}
snd_stm_magic_set(source);
source->bus = bus;
strlcpy(source->bus_id, bus_id, SND_STM_BUS_ID_SIZE);
INIT_LIST_HEAD(&source->groups);
list_add_tail(&source->list, &snd_stm_conv_sources);
done:
mutex_unlock(&snd_stm_conv_mutex);
return source;
}
struct snd_stm_conv_source *snd_stm_conv_register_source(struct bus_type *bus,
const char *bus_id, int channels_num,
struct snd_card *card, int card_device)
{
struct snd_stm_conv_source *source;
struct snd_stm_conv_group *group;
snd_stm_printd(1, "snd_stm_conv_register_source(bus=%p, bus_id='%s', "
"channels_num=%d, card=%p, card_device=%d)\n",
bus, bus_id, channels_num, card, card_device);
BUG_ON(!bus);
BUG_ON(!bus_id);
BUG_ON(channels_num <= 0);
BUG_ON(!card);
BUG_ON(card_device < 0);
source = snd_stm_conv_get_source(bus, bus_id);
if (!source) {
snd_stm_printe("Can't get source structure!\n");
return NULL;
}
BUG_ON(source->channels_num != 0);
BUG_ON(source->card);
source->channels_num = channels_num;
source->card = card;
source->card_device = card_device;
mutex_lock(&snd_stm_conv_mutex);
/* Add route ALSA control if needed */
if (snd_stm_conv_more_than_one_entry(&source->groups) &&
snd_stm_conv_ctl_route_add(source) != 0)
snd_stm_printe("Failed to add route ALSA control!\n");
/* Add mute ALSA controls for already registered converters */
list_for_each_entry(group, &source->groups, list) {
struct snd_stm_conv_converter *converter;
BUG_ON(!snd_stm_magic_valid(group));
list_for_each_entry(converter, &group->converters, list) {
BUG_ON(!snd_stm_magic_valid(converter));
if (converter->ops->set_muted &&
snd_stm_conv_ctl_mute_add(converter)
< 0)
snd_stm_printe("Failed to add mute "
"ALSA control!\n");
}
}
mutex_unlock(&snd_stm_conv_mutex);
return source;
}
int snd_stm_conv_unregister_source(struct snd_stm_conv_source *source)
{
snd_stm_printd(1, "snd_stm_conv_unregister_source(source=%p)\n",
source);
BUG_ON(!source);
BUG_ON(!snd_stm_magic_valid(source));
mutex_lock(&snd_stm_conv_mutex);
list_del(&source->list);
source->channels_num = 0;
/* If there is no more registered converters... */
if (list_empty(&source->groups)) {
snd_stm_magic_clear(source);
kfree(source);
}
mutex_unlock(&snd_stm_conv_mutex);
return 0;
}
static struct snd_stm_conv_group *snd_stm_conv_get_group(
struct snd_stm_conv_source *source, const char *name)
{
struct snd_stm_conv_group *group;
snd_stm_printd(1, "snd_stm_conv_get_group(source=%p, name='%s')\n",
source, name);
BUG_ON(!source);
BUG_ON(!snd_stm_magic_valid(source));
BUG_ON(!name);
/* Random memory fuse */
BUG_ON(strlen(name) > 1024);
mutex_lock(&snd_stm_conv_mutex);
list_for_each_entry(group, &source->groups, list)
if (strcmp(name, group->name) == 0)
goto done; /* Already known group */
/* First time see... */
group = kzalloc(sizeof(*group) + strlen(name), GFP_KERNEL);
if (!group) {
snd_stm_printe("Can't allocate memory for group!\n");
goto done;
}
snd_stm_magic_set(group);
INIT_LIST_HEAD(&group->converters);
strcpy(group->name, name);
group->source = source;
group->muted_by_source = 1;
if (!source->group_selected)
source->group_selected = group;
list_add_tail(&group->list, &source->groups);
/* If this is a second group attached to this source,
* and the source has been already registered, we need
* to add a route ALSA control... */
if (source->card && snd_stm_conv_more_than_one_entry(&source->groups))
snd_stm_conv_ctl_route_add(source);
done:
mutex_unlock(&snd_stm_conv_mutex);
return group;
}
static int snd_stm_conv_remove_group(struct snd_stm_conv_group *group)
{
struct snd_stm_conv_source *source;
snd_stm_printd(1, "snd_stm_conv_remove_group(group=%p)\n", group);
BUG_ON(!group);
BUG_ON(!snd_stm_magic_valid(group));
source = group->source;
BUG_ON(!source);
BUG_ON(!snd_stm_magic_valid(source));
list_del(&group->list);
if (group == source->group_active)
snd_stm_printe("WARNING! Removing active converters group! "
"I hope you know what are you doing...\n");
/* Removing actually selected group? */
if (group == source->group_selected) {
if (list_empty(&source->groups)) {
/* This was the last group... */
source->group_selected = NULL;
} else {
/* If there is only one group left, the route control
* is not needed anymore */
if (!snd_stm_conv_more_than_one_entry(&source->groups)
&& source->ctl_route)
snd_ctl_remove(source->card, source->ctl_route);
/* The first group on list is considered default... */
source->group_selected = list_first_entry(
&source->groups,
struct snd_stm_conv_group, list);
}
}
snd_stm_magic_clear(group);
kfree(group);
/* Release the source resources, if not used anymore */
if (list_empty(&source->groups) && source->channels_num == 0) {
snd_stm_magic_clear(source);
kfree(source);
}
return 0;
}
struct snd_stm_conv_converter *snd_stm_conv_register_converter(
const char *group_name, struct snd_stm_conv_ops *ops,
void *priv,
struct bus_type *source_bus, const char *source_bus_id,
int source_channel_from, int source_channel_to, int *index)
{
static int number;
struct snd_stm_conv_converter *converter;
struct snd_stm_conv_group *group;
struct snd_stm_conv_source *source;
snd_stm_printd(1, "snd_stm_conv_register_converter(group='%s', ops=%p,"
" priv=%p, source_bus=%p, source_bus_id='%s', "
"source_channel_from=%d, source_channel_to=%d)\n",
group_name, ops, priv, source_bus, source_bus_id,
source_channel_from, source_channel_to);
BUG_ON(!group_name);
BUG_ON(!ops);
BUG_ON(!source_bus);
BUG_ON(!source_bus_id);
BUG_ON(source_channel_from < 0);
BUG_ON(source_channel_to < source_channel_from);
/* Create converter description */
converter = kzalloc(sizeof(*converter), GFP_KERNEL);
if (!converter) {
snd_stm_printe("Can't allocate memory for converter!\n");
goto error_kzalloc;
}
snd_stm_magic_set(converter);
converter->ops = ops;
converter->priv = priv;
converter->source_channel_from = source_channel_from;
converter->source_channel_to = source_channel_to;
converter->muted_by_source = 1;
spin_lock_init(&converter->status_lock);
/* And link it with the source */
source = snd_stm_conv_get_source(source_bus, source_bus_id);
if (!source) {
snd_stm_printe("Can't get source structure!\n");
goto error_get_source;
}
group = snd_stm_conv_get_group(source, group_name);
if (!group) {
snd_stm_printe("Can't get group structure!\n");
goto error_get_group;
}
if (source->group_active == group)
snd_stm_printe("WARNING! Adding a converter to an active "
"group!\n");
mutex_lock(&snd_stm_conv_mutex);
converter->group = group;
list_add_tail(&converter->list, &group->converters);
mutex_unlock(&snd_stm_conv_mutex);
/* Add mute ALSA control if muting is supported and source is known */
if (source->card && ops->set_muted &&
snd_stm_conv_ctl_mute_add(converter) < 0) {
snd_stm_printe("Failed to add mute ALSA control!\n");
goto error_add_ctl_mute;
}
number++;
if (index)
*index = number;
return converter;
error_add_ctl_mute:
error_get_group:
error_get_source:
list_del(&converter->list);
snd_stm_magic_clear(converter);
kfree(converter);
error_kzalloc:
return NULL;
}
EXPORT_SYMBOL(snd_stm_conv_register_converter);
int snd_stm_conv_unregister_converter(struct snd_stm_conv_converter *converter)
{
struct snd_stm_conv_group *group;
snd_stm_printd(1, "snd_stm_conv_unregister_converter(converter=%p)\n",
converter);
BUG_ON(!converter);
BUG_ON(!snd_stm_magic_valid(converter));
group = converter->group;
BUG_ON(!group);
BUG_ON(!snd_stm_magic_valid(group));
mutex_lock(&snd_stm_conv_mutex);
list_del(&converter->list);
if (converter->ctl_mute)
snd_ctl_remove(group->source->card, converter->ctl_mute);
snd_stm_magic_clear(converter);
kfree(converter);
if (list_empty(&group->converters))
snd_stm_conv_remove_group(group);
mutex_unlock(&snd_stm_conv_mutex);
return 0;
}
EXPORT_SYMBOL(snd_stm_conv_unregister_converter);
int snd_stm_conv_get_card_device(struct snd_stm_conv_converter *converter)
{
snd_stm_printd(1, "snd_stm_conv_get_card_device(converter=%p)\n",
converter);
BUG_ON(!converter);
BUG_ON(!snd_stm_magic_valid(converter));
return converter->group->source->card_device;
}
EXPORT_SYMBOL(snd_stm_conv_get_card_device);
struct snd_stm_conv_group *snd_stm_conv_request_group(
struct snd_stm_conv_source *source)
{
snd_stm_printd(1, "snd_stm_conv_request_group(source=%p)\n", source);
BUG_ON(!source);
BUG_ON(!snd_stm_magic_valid(source));
BUG_ON(source->group_active);
mutex_lock(&snd_stm_conv_mutex);
source->group_active = source->group_selected;
mutex_unlock(&snd_stm_conv_mutex);
return source->group_active;
}
int snd_stm_conv_release_group(struct snd_stm_conv_group *group)
{
snd_stm_printd(1, "snd_stm_conv_release_group(group=%p)\n", group);
BUG_ON(!group);
BUG_ON(!snd_stm_magic_valid(group));
BUG_ON(!group->source);
BUG_ON(!snd_stm_magic_valid(group->source));
BUG_ON(group != group->source->group_active);
mutex_lock(&snd_stm_conv_mutex);
group->source->group_active = NULL;
mutex_unlock(&snd_stm_conv_mutex);
return 0;
}
/*
* Converters information view
*/
static struct snd_info_entry *snd_stm_conv_proc_entry;
static void snd_stm_conv_info(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_stm_conv_source *source;
snd_iprintf(buffer, "--- converters ---\n");
list_for_each_entry(source, &snd_stm_conv_sources, list) {
struct snd_stm_conv_group *group;
snd_iprintf(buffer, "- source %p:\n", source);
snd_iprintf(buffer, " bus=%p, bus_id='%s'\n",
source->bus, source->bus_id);
snd_iprintf(buffer, " channels_num=%d\n",
source->channels_num);
snd_iprintf(buffer, " card=%p, card_device=%d\n",
source->card, source->card_device);
list_for_each_entry(group, &source->groups, list) {
struct snd_stm_conv_converter *converter;
snd_iprintf(buffer, " - group %p:\n", group);
snd_iprintf(buffer, " name='%s'\n", group->name);
list_for_each_entry(converter, &group->converters,
list) {
snd_iprintf(buffer, " - converter %p:\n",
converter);
snd_iprintf(buffer, " source_channel_from"
"=%d, source_channel_to=%d\n",
converter->source_channel_from,
converter->source_channel_to);
snd_iprintf(buffer, " enabled=%d\n",
converter->enabled);
snd_iprintf(buffer, " muted_by_source=%d,"
" muted_by_user=%d\n",
converter->muted_by_source,
converter->muted_by_user);
}
}
}
snd_iprintf(buffer, "\n");
}
/*
* Initialization
*/
int __init snd_stm_conv_init(void)
{
/* Register converters information file in ALSA's procfs */
snd_stm_info_register(&snd_stm_conv_proc_entry, "converters",
snd_stm_conv_info, NULL);
return 0;
}
void snd_stm_conv_exit(void)
{
snd_stm_info_unregister(snd_stm_conv_proc_entry);
if (!list_empty(&snd_stm_conv_sources)) {
struct snd_stm_conv_source *source, *source_next;
snd_stm_printe("WARNING! There are some converters "
"infrastructure components left - "
"check your configuration!\n");
list_for_each_entry_safe(source, source_next,
&snd_stm_conv_sources, list) {
struct snd_stm_conv_group *group, *group_next;
list_for_each_entry_safe(group, group_next,
&source->groups, list) {
struct snd_stm_conv_converter *converter,
*converter_next;
list_for_each_entry_safe(converter,
converter_next,
&group->converters, list) {
list_del(&converter->list);
snd_stm_magic_clear(converter);
kfree(converter);
}
list_del(&group->list);
snd_stm_magic_clear(group);
kfree(group);
}
list_del(&source->list);
snd_stm_magic_clear(source);
kfree(source);
}
}
}