| /* | |
| * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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; | |
| 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; | |
| } |