blob: bb2f59d8a18a8f8a80a19ac220cc83d0163fae28 [file] [log] [blame]
/*
* Mixer Interface - simple abstact module - base library
* Copyright (c) 2005 by Jaroslav Kysela <perex@perex.cz>
*
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <math.h>
#include "asoundlib.h"
#include "mixer_abst.h"
#include "sbase.h"
/*
* Prototypes
*/
static int selem_read(snd_mixer_elem_t *elem);
/*
* Helpers
*/
static unsigned int chanmap_to_channels(unsigned int chanmap)
{
unsigned int i, res;
for (i = 0, res = 0; i < MAX_CHANNEL; i++)
if (chanmap & (1 << i))
res++;
return res;
}
#if 0
static long to_user(struct selem_base *s, int dir, struct helem_base *c, long value)
{
int64_t n;
if (c->max == c->min)
return s->dir[dir].min;
n = (int64_t) (value - c->min) * (s->dir[dir].max - s->dir[dir].min);
return s->dir[dir].min + (n + (c->max - c->min) / 2) / (c->max - c->min);
}
static long from_user(struct selem_base *s, int dir, struct helem_base *c, long value)
{
int64_t n;
if (s->dir[dir].max == s->dir[dir].min)
return c->min;
n = (int64_t) (value - s->dir[dir].min) * (c->max - c->min);
return c->min + (n + (s->dir[dir].max - s->dir[dir].min) / 2) / (s->dir[dir].max - s->dir[dir].min);
}
#endif
static void update_ranges(struct selem_base *s)
{
static unsigned int mask[2] = { SM_CAP_PVOLUME, SM_CAP_CVOLUME };
static unsigned int gmask[2] = { SM_CAP_GVOLUME, SM_CAP_GVOLUME };
unsigned int dir, ok_flag;
struct list_head *pos;
struct helem_base *helem;
for (dir = 0; dir < 2; dir++) {
s->dir[dir].min = 0;
s->dir[dir].max = 0;
ok_flag = 0;
list_for_each(pos, &s->helems) {
helem = list_entry(pos, struct helem_base, list);
printf("min = %li, max = %li\n", helem->min, helem->max);
if (helem->caps & mask[dir]) {
s->dir[dir].min = helem->min;
s->dir[dir].max = helem->max;
ok_flag = 1;
break;
}
}
if (ok_flag)
continue;
list_for_each(pos, &s->helems) {
helem = list_entry(pos, struct helem_base, list);
if (helem->caps & gmask[dir]) {
s->dir[dir].min = helem->min;
s->dir[dir].max = helem->max;
break;
}
}
}
}
/*
* Simple Mixer Operations
*/
static int is_ops(snd_mixer_elem_t *elem, int dir, int cmd, int val)
{
struct selem_base *s = snd_mixer_elem_get_private(elem);
switch (cmd) {
case SM_OPS_IS_ACTIVE: {
struct list_head *pos;
struct helem_base *helem;
list_for_each(pos, &s->helems) {
helem = list_entry(pos, struct helem_base, list);
if (helem->inactive)
return 0;
}
return 1;
}
case SM_OPS_IS_MONO:
return chanmap_to_channels(s->dir[dir].chanmap) == 1;
case SM_OPS_IS_CHANNEL:
if (val > MAX_CHANNEL)
return 0;
return !!((1 << val) & s->dir[dir].chanmap);
case SM_OPS_IS_ENUMERATED: {
struct helem_base *helem;
helem = list_entry(s->helems.next, struct helem_base, list);
return !!(helem->purpose == PURPOSE_ENUMLIST);
}
case SM_OPS_IS_ENUMCNT: {
struct helem_base *helem;
helem = list_entry(s->helems.next, struct helem_base, list);
return helem->max;
}
}
return 1;
}
static int get_range_ops(snd_mixer_elem_t *elem, int dir,
long *min, long *max)
{
struct selem_base *s = snd_mixer_elem_get_private(elem);
*min = s->dir[dir].min;
*max = s->dir[dir].max;
return 0;
}
static int get_dB_range_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
int dir ATTRIBUTE_UNUSED,
long *min ATTRIBUTE_UNUSED,
long *max ATTRIBUTE_UNUSED)
{
return -ENXIO;
}
static int set_range_ops(snd_mixer_elem_t *elem, int dir,
long min, long max)
{
struct selem_base *s = snd_mixer_elem_get_private(elem);
int err;
s->dir[dir].forced_range = 1;
s->dir[dir].min = min;
s->dir[dir].max = max;
if ((err = selem_read(elem)) < 0)
return err;
return 0;
}
static int get_volume_ops(snd_mixer_elem_t *elem, int dir,
snd_mixer_selem_channel_id_t channel, long *value)
{
struct selem_base *s = snd_mixer_elem_get_private(elem);
*value = s->dir[dir].vol[channel];
return 0;
}
static int get_dB_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
int dir ATTRIBUTE_UNUSED,
snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
long *value ATTRIBUTE_UNUSED)
{
return -ENXIO;
}
static int get_switch_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
int dir ATTRIBUTE_UNUSED,
snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
int *value)
{
/* struct selem_base *s = snd_mixer_elem_get_private(elem); */
*value = 0;
return 0;
}
static int set_volume_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
int dir ATTRIBUTE_UNUSED,
snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
long value ATTRIBUTE_UNUSED)
{
/* struct selem_base *s = snd_mixer_elem_get_private(elem); */
return 0;
}
static int set_dB_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
int dir ATTRIBUTE_UNUSED,
snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
long value ATTRIBUTE_UNUSED,
int xdir ATTRIBUTE_UNUSED)
{
return -ENXIO;
}
static int set_switch_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
int dir ATTRIBUTE_UNUSED,
snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
int value ATTRIBUTE_UNUSED)
{
/* struct selem_base *s = snd_mixer_elem_get_private(elem); */
/* int changed; */
return 0;
}
static int enum_item_name_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
unsigned int item ATTRIBUTE_UNUSED,
size_t maxlen ATTRIBUTE_UNUSED,
char *buf ATTRIBUTE_UNUSED)
{
/* struct selem_base *s = snd_mixer_elem_get_private(elem);*/
return 0;
}
static int get_enum_item_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
unsigned int *itemp ATTRIBUTE_UNUSED)
{
/* struct selem_base *s = snd_mixer_elem_get_private(elem); */
return 0;
}
static int set_enum_item_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
unsigned int item ATTRIBUTE_UNUSED)
{
/* struct selem_base *s = snd_mixer_elem_get_private(elem); */
return 0;
}
static struct sm_elem_ops simple_ac97_ops = {
.is = is_ops,
.get_range = get_range_ops,
.get_dB_range = get_dB_range_ops,
.set_range = set_range_ops,
.get_volume = get_volume_ops,
.get_dB = get_dB_ops,
.set_volume = set_volume_ops,
.set_dB = set_dB_ops,
.get_switch = get_switch_ops,
.set_switch = set_switch_ops,
.enum_item_name = enum_item_name_ops,
.get_enum_item = get_enum_item_ops,
.set_enum_item = set_enum_item_ops
};
/*
* event handling
*/
static int selem_read(snd_mixer_elem_t *elem)
{
printf("elem read: %p\n", elem);
return 0;
}
static int simple_event_remove(snd_hctl_elem_t *helem,
snd_mixer_elem_t *melem ATTRIBUTE_UNUSED)
{
printf("event remove: %p\n", helem);
return 0;
}
static void selem_free(snd_mixer_elem_t *elem)
{
struct selem_base *simple = snd_mixer_elem_get_private(elem);
struct helem_base *hsimple;
struct list_head *pos, *npos;
if (simple->selem.id)
snd_mixer_selem_id_free(simple->selem.id);
list_for_each_safe(pos, npos, &simple->helems) {
hsimple = list_entry(pos, struct helem_base, list);
free(hsimple);
}
free(simple);
}
static int simple_event_add1(snd_mixer_class_t *class,
snd_hctl_elem_t *helem,
struct helem_selector *sel)
{
struct bclass_private *priv = snd_mixer_sbasic_get_private(class);
snd_mixer_elem_t *melem;
snd_mixer_selem_id_t *id;
snd_ctl_elem_info_t *info;
struct selem_base *simple;
struct helem_base *hsimple;
snd_ctl_elem_type_t ctype;
unsigned long values;
long min, max;
int err, new = 0;
struct list_head *pos;
struct bclass_sid *bsid;
struct melem_sids *sid;
unsigned int ui;
list_for_each(pos, &priv->sids) {
bsid = list_entry(pos, struct bclass_sid, list);
for (ui = 0; ui < bsid->count; ui++) {
if (bsid->sids[ui].sid == sel->sid) {
sid = &bsid->sids[ui];
goto __sid_ok;
}
}
}
return 0;
__sid_ok:
snd_ctl_elem_info_alloca(&info);
err = snd_hctl_elem_info(helem, info);
if (err < 0)
return err;
ctype = snd_ctl_elem_info_get_type(info);
values = snd_ctl_elem_info_get_count(info);
switch (ctype) {
case SND_CTL_ELEM_TYPE_ENUMERATED:
min = 0;
max = snd_ctl_elem_info_get_items(info);
break;
case SND_CTL_ELEM_TYPE_INTEGER:
min = snd_ctl_elem_info_get_min(info);
max = snd_ctl_elem_info_get_max(info);
break;
default:
min = max = 0;
break;
}
printf("event add: %p, %p (%s)\n", helem, sel, snd_hctl_elem_get_name(helem));
if (snd_mixer_selem_id_malloc(&id))
return -ENOMEM;
hsimple = calloc(1, sizeof(*hsimple));
if (hsimple == NULL) {
snd_mixer_selem_id_free(id);
return -ENOMEM;
}
switch (sel->purpose) {
case PURPOSE_SWITCH:
if (ctype != SND_CTL_ELEM_TYPE_BOOLEAN) {
__invalid_type:
snd_mixer_selem_id_free(id);
free(hsimple);
return -EINVAL;
}
break;
case PURPOSE_VOLUME:
if (ctype != SND_CTL_ELEM_TYPE_INTEGER)
goto __invalid_type;
break;
}
hsimple->purpose = sel->purpose;
hsimple->caps = sel->caps;
hsimple->min = min;
hsimple->max = max;
snd_mixer_selem_id_set_name(id, sid->sname);
snd_mixer_selem_id_set_index(id, sid->sindex);
melem = snd_mixer_find_selem(snd_mixer_class_get_mixer(class), id);
if (!melem) {
simple = calloc(1, sizeof(*simple));
if (!simple) {
snd_mixer_selem_id_free(id);
free(hsimple);
return -ENOMEM;
}
simple->selem.id = id;
simple->selem.ops = &simple_ac97_ops;
INIT_LIST_HEAD(&simple->helems);
simple->sid = sel->sid;
err = snd_mixer_elem_new(&melem, SND_MIXER_ELEM_SIMPLE,
sid->weight,
simple, selem_free);
if (err < 0) {
snd_mixer_selem_id_free(id);
free(hsimple);
free(simple);
return err;
}
new = 1;
} else {
simple = snd_mixer_elem_get_private(melem);
snd_mixer_selem_id_free(id);
}
list_add_tail(&hsimple->list, &simple->helems);
hsimple->inactive = snd_ctl_elem_info_is_inactive(info);
err = snd_mixer_elem_attach(melem, helem);
if (err < 0)
goto __error;
simple->dir[0].chanmap |= sid->chanmap[0];
simple->dir[1].chanmap |= sid->chanmap[1];
simple->selem.caps |= hsimple->caps;
update_ranges(simple);
#if 0
err = simple_update(melem);
if (err < 0) {
if (new)
goto __error;
return err;
}
#endif
if (new)
err = snd_mixer_elem_add(melem, class);
else
err = snd_mixer_elem_info(melem);
if (err < 0)
return err;
err = selem_read(melem);
if (err < 0)
return err;
if (err)
err = snd_mixer_elem_value(melem);
return err;
__error:
if (new)
snd_mixer_elem_free(melem);
return -EINVAL;
}
static int simple_event_add(snd_mixer_class_t *class, snd_hctl_elem_t *helem)
{
struct bclass_private *priv = snd_mixer_sbasic_get_private(class);
struct bclass_selector *sel;
struct helem_selector *hsel;
struct list_head *pos;
snd_ctl_elem_iface_t iface = snd_hctl_elem_get_interface(helem);
const char *name = snd_hctl_elem_get_name(helem);
unsigned int index = snd_hctl_elem_get_index(helem);
unsigned int ui;
int err;
list_for_each(pos, &priv->selectors) {
sel = list_entry(pos, struct bclass_selector, list);
for (ui = 0; ui < sel->count; ui++) {
hsel = &sel->selectors[ui];
if (hsel->iface == iface && !strcmp(hsel->name, name) && hsel->index == index) {
err = simple_event_add1(class, helem, hsel);
if (err < 0)
return err; /* early exit? */
}
}
}
return 0;
}
int alsa_mixer_sbasic_event(snd_mixer_class_t *class, unsigned int mask,
snd_hctl_elem_t *helem, snd_mixer_elem_t *melem)
{
int err;
if (mask == SND_CTL_EVENT_MASK_REMOVE)
return simple_event_remove(helem, melem);
if (mask & SND_CTL_EVENT_MASK_ADD) {
err = simple_event_add(class, helem);
if (err < 0)
return err;
}
if (mask & SND_CTL_EVENT_MASK_INFO) {
err = simple_event_remove(helem, melem);
if (err < 0)
return err;
err = simple_event_add(class, helem);
if (err < 0)
return err;
return 0;
}
if (mask & SND_CTL_EVENT_MASK_VALUE) {
err = selem_read(melem);
if (err < 0)
return err;
if (err) {
err = snd_mixer_elem_value(melem);
if (err < 0)
return err;
}
}
return 0;
}
static void sbasic_cpriv_free(snd_mixer_class_t *class)
{
struct bclass_private *priv = snd_mixer_sbasic_get_private(class);
struct bclass_selector *sel;
struct bclass_sid *sid;
struct list_head *pos, *pos1;
list_for_each_safe(pos, pos1, &priv->selectors) {
sel = list_entry(pos, struct bclass_selector, list);
free(sel);
}
list_for_each_safe(pos, pos1, &priv->sids) {
sid = list_entry(pos, struct bclass_sid, list);
free(sid);
}
free(priv);
}
void alsa_mixer_sbasic_initpriv(snd_mixer_class_t *class,
struct bclass_private *priv)
{
INIT_LIST_HEAD(&priv->selectors);
INIT_LIST_HEAD(&priv->sids);
snd_mixer_sbasic_set_private(class, priv);
snd_mixer_sbasic_set_private_free(class, sbasic_cpriv_free);
}
int alsa_mixer_sbasic_selreg(snd_mixer_class_t *class,
struct helem_selector *selectors,
unsigned int count)
{
struct bclass_private *priv = snd_mixer_sbasic_get_private(class);
struct bclass_selector *sel = calloc(1, sizeof(*sel));
if (sel == NULL)
return -ENOMEM;
if (priv == NULL) {
priv = calloc(1, sizeof(*priv));
if (priv == NULL) {
free(sel);
return -ENOMEM;
}
}
sel->selectors = selectors;
sel->count = count;
list_add_tail(&sel->list, &priv->selectors);
return 0;
}
int alsa_mixer_sbasic_sidreg(snd_mixer_class_t *class,
struct melem_sids *sids,
unsigned int count)
{
struct bclass_private *priv = snd_mixer_sbasic_get_private(class);
struct bclass_sid *sid = calloc(1, sizeof(*sid));
if (sid == NULL)
return -ENOMEM;
if (priv == NULL) {
priv = calloc(1, sizeof(*priv));
if (priv == NULL) {
free(sid);
return -ENOMEM;
}
INIT_LIST_HEAD(&priv->selectors);
INIT_LIST_HEAD(&priv->sids);
snd_mixer_sbasic_set_private(class, priv);
snd_mixer_sbasic_set_private_free(class, sbasic_cpriv_free);
}
sid->sids = sids;
sid->count = count;
list_add(&sid->list, &priv->sids);
return 0;
}