/* * Mixer Interface - simple abstact module - base library * Copyright (c) 2005 by Jaroslav Kysela * * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include #include #include #include #include #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; 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); 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; }