| /* |
| * 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; |
| } |