1 /*
2 * Mixer Interface - simple abstact module - base library
3 * Copyright (c) 2005 by Jaroslav Kysela <perex@perex.cz>
4 *
5 *
6 * This library is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as
8 * published by the Free Software Foundation; either version 2.1 of
9 * the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 *
20 */
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <string.h>
26 #include <fcntl.h>
27 #include <sys/ioctl.h>
28 #include <math.h>
29 #include "asoundlib.h"
30 #include "mixer_abst.h"
31 #include "sbase.h"
32
33 /*
34 * Prototypes
35 */
36
37 static int selem_read(snd_mixer_elem_t *elem);
38
39 /*
40 * Helpers
41 */
42
chanmap_to_channels(unsigned int chanmap)43 static unsigned int chanmap_to_channels(unsigned int chanmap)
44 {
45 unsigned int i, res;
46
47 for (i = 0, res = 0; i < MAX_CHANNEL; i++)
48 if (chanmap & (1 << i))
49 res++;
50 return res;
51 }
52
53 #if 0
54 static long to_user(struct selem_base *s, int dir, struct helem_base *c, long value)
55 {
56 int64_t n;
57 if (c->max == c->min)
58 return s->dir[dir].min;
59 n = (int64_t) (value - c->min) * (s->dir[dir].max - s->dir[dir].min);
60 return s->dir[dir].min + (n + (c->max - c->min) / 2) / (c->max - c->min);
61 }
62
63 static long from_user(struct selem_base *s, int dir, struct helem_base *c, long value)
64 {
65 int64_t n;
66 if (s->dir[dir].max == s->dir[dir].min)
67 return c->min;
68 n = (int64_t) (value - s->dir[dir].min) * (c->max - c->min);
69 return c->min + (n + (s->dir[dir].max - s->dir[dir].min) / 2) / (s->dir[dir].max - s->dir[dir].min);
70 }
71 #endif
72
update_ranges(struct selem_base * s)73 static void update_ranges(struct selem_base *s)
74 {
75 static unsigned int mask[2] = { SM_CAP_PVOLUME, SM_CAP_CVOLUME };
76 static unsigned int gmask[2] = { SM_CAP_GVOLUME, SM_CAP_GVOLUME };
77 unsigned int dir, ok_flag;
78 struct list_head *pos;
79 struct helem_base *helem;
80
81 for (dir = 0; dir < 2; dir++) {
82 s->dir[dir].min = 0;
83 s->dir[dir].max = 0;
84 ok_flag = 0;
85 list_for_each(pos, &s->helems) {
86 helem = list_entry(pos, struct helem_base, list);
87 printf("min = %li, max = %li\n", helem->min, helem->max);
88 if (helem->caps & mask[dir]) {
89 s->dir[dir].min = helem->min;
90 s->dir[dir].max = helem->max;
91 ok_flag = 1;
92 break;
93 }
94 }
95 if (ok_flag)
96 continue;
97 list_for_each(pos, &s->helems) {
98 helem = list_entry(pos, struct helem_base, list);
99 if (helem->caps & gmask[dir]) {
100 s->dir[dir].min = helem->min;
101 s->dir[dir].max = helem->max;
102 break;
103 }
104 }
105 }
106 }
107
108 /*
109 * Simple Mixer Operations
110 */
111
is_ops(snd_mixer_elem_t * elem,int dir,int cmd,int val)112 static int is_ops(snd_mixer_elem_t *elem, int dir, int cmd, int val)
113 {
114 struct selem_base *s = snd_mixer_elem_get_private(elem);
115
116 switch (cmd) {
117
118 case SM_OPS_IS_ACTIVE: {
119 struct list_head *pos;
120 struct helem_base *helem;
121 list_for_each(pos, &s->helems) {
122 helem = list_entry(pos, struct helem_base, list);
123 if (helem->inactive)
124 return 0;
125 }
126 return 1;
127 }
128
129 case SM_OPS_IS_MONO:
130 return chanmap_to_channels(s->dir[dir].chanmap) == 1;
131
132 case SM_OPS_IS_CHANNEL:
133 if (val > MAX_CHANNEL)
134 return 0;
135 return !!((1 << val) & s->dir[dir].chanmap);
136
137 case SM_OPS_IS_ENUMERATED: {
138 struct helem_base *helem;
139 helem = list_entry(s->helems.next, struct helem_base, list);
140 return !!(helem->purpose == PURPOSE_ENUMLIST);
141 }
142
143 case SM_OPS_IS_ENUMCNT: {
144 struct helem_base *helem;
145 helem = list_entry(s->helems.next, struct helem_base, list);
146 return helem->max;
147 }
148
149 }
150
151 return 1;
152 }
153
get_range_ops(snd_mixer_elem_t * elem,int dir,long * min,long * max)154 static int get_range_ops(snd_mixer_elem_t *elem, int dir,
155 long *min, long *max)
156 {
157 struct selem_base *s = snd_mixer_elem_get_private(elem);
158
159 *min = s->dir[dir].min;
160 *max = s->dir[dir].max;
161
162 return 0;
163 }
164
get_dB_range_ops(snd_mixer_elem_t * elem ATTRIBUTE_UNUSED,int dir ATTRIBUTE_UNUSED,long * min ATTRIBUTE_UNUSED,long * max ATTRIBUTE_UNUSED)165 static int get_dB_range_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
166 int dir ATTRIBUTE_UNUSED,
167 long *min ATTRIBUTE_UNUSED,
168 long *max ATTRIBUTE_UNUSED)
169 {
170 return -ENXIO;
171 }
172
set_range_ops(snd_mixer_elem_t * elem,int dir,long min,long max)173 static int set_range_ops(snd_mixer_elem_t *elem, int dir,
174 long min, long max)
175 {
176 struct selem_base *s = snd_mixer_elem_get_private(elem);
177 int err;
178
179 s->dir[dir].forced_range = 1;
180 s->dir[dir].min = min;
181 s->dir[dir].max = max;
182
183 if ((err = selem_read(elem)) < 0)
184 return err;
185 return 0;
186 }
187
get_volume_ops(snd_mixer_elem_t * elem,int dir,snd_mixer_selem_channel_id_t channel,long * value)188 static int get_volume_ops(snd_mixer_elem_t *elem, int dir,
189 snd_mixer_selem_channel_id_t channel, long *value)
190 {
191 struct selem_base *s = snd_mixer_elem_get_private(elem);
192
193 *value = s->dir[dir].vol[channel];
194 return 0;
195 }
196
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)197 static int get_dB_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
198 int dir ATTRIBUTE_UNUSED,
199 snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
200 long *value ATTRIBUTE_UNUSED)
201 {
202 return -ENXIO;
203 }
204
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)205 static int get_switch_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
206 int dir ATTRIBUTE_UNUSED,
207 snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
208 int *value)
209 {
210 /* struct selem_base *s = snd_mixer_elem_get_private(elem); */
211 *value = 0;
212 return 0;
213 }
214
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)215 static int set_volume_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
216 int dir ATTRIBUTE_UNUSED,
217 snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
218 long value ATTRIBUTE_UNUSED)
219 {
220 /* struct selem_base *s = snd_mixer_elem_get_private(elem); */
221 return 0;
222 }
223
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)224 static int set_dB_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
225 int dir ATTRIBUTE_UNUSED,
226 snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
227 long value ATTRIBUTE_UNUSED,
228 int xdir ATTRIBUTE_UNUSED)
229 {
230 return -ENXIO;
231 }
232
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)233 static int set_switch_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
234 int dir ATTRIBUTE_UNUSED,
235 snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
236 int value ATTRIBUTE_UNUSED)
237 {
238 /* struct selem_base *s = snd_mixer_elem_get_private(elem); */
239 /* int changed; */
240 return 0;
241 }
242
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)243 static int enum_item_name_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
244 unsigned int item ATTRIBUTE_UNUSED,
245 size_t maxlen ATTRIBUTE_UNUSED,
246 char *buf ATTRIBUTE_UNUSED)
247 {
248 /* struct selem_base *s = snd_mixer_elem_get_private(elem);*/
249 return 0;
250 }
251
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)252 static int get_enum_item_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
253 snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
254 unsigned int *itemp ATTRIBUTE_UNUSED)
255 {
256 /* struct selem_base *s = snd_mixer_elem_get_private(elem); */
257 return 0;
258 }
259
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)260 static int set_enum_item_ops(snd_mixer_elem_t *elem ATTRIBUTE_UNUSED,
261 snd_mixer_selem_channel_id_t channel ATTRIBUTE_UNUSED,
262 unsigned int item ATTRIBUTE_UNUSED)
263 {
264 /* struct selem_base *s = snd_mixer_elem_get_private(elem); */
265 return 0;
266 }
267
268 static struct sm_elem_ops simple_ac97_ops = {
269 .is = is_ops,
270 .get_range = get_range_ops,
271 .get_dB_range = get_dB_range_ops,
272 .set_range = set_range_ops,
273 .get_volume = get_volume_ops,
274 .get_dB = get_dB_ops,
275 .set_volume = set_volume_ops,
276 .set_dB = set_dB_ops,
277 .get_switch = get_switch_ops,
278 .set_switch = set_switch_ops,
279 .enum_item_name = enum_item_name_ops,
280 .get_enum_item = get_enum_item_ops,
281 .set_enum_item = set_enum_item_ops
282 };
283
284 /*
285 * event handling
286 */
287
selem_read(snd_mixer_elem_t * elem)288 static int selem_read(snd_mixer_elem_t *elem)
289 {
290 printf("elem read: %p\n", elem);
291 return 0;
292 }
293
simple_event_remove(snd_hctl_elem_t * helem,snd_mixer_elem_t * melem ATTRIBUTE_UNUSED)294 static int simple_event_remove(snd_hctl_elem_t *helem,
295 snd_mixer_elem_t *melem ATTRIBUTE_UNUSED)
296 {
297 printf("event remove: %p\n", helem);
298 return 0;
299 }
300
selem_free(snd_mixer_elem_t * elem)301 static void selem_free(snd_mixer_elem_t *elem)
302 {
303 struct selem_base *simple = snd_mixer_elem_get_private(elem);
304 struct helem_base *hsimple;
305 struct list_head *pos, *npos;
306
307 if (simple->selem.id)
308 snd_mixer_selem_id_free(simple->selem.id);
309 list_for_each_safe(pos, npos, &simple->helems) {
310 hsimple = list_entry(pos, struct helem_base, list);
311 free(hsimple);
312 }
313 free(simple);
314 }
315
simple_event_add1(snd_mixer_class_t * class,snd_hctl_elem_t * helem,struct helem_selector * sel)316 static int simple_event_add1(snd_mixer_class_t *class,
317 snd_hctl_elem_t *helem,
318 struct helem_selector *sel)
319 {
320 struct bclass_private *priv = snd_mixer_sbasic_get_private(class);
321 snd_mixer_elem_t *melem;
322 snd_mixer_selem_id_t *id;
323 snd_ctl_elem_info_t *info;
324 struct selem_base *simple;
325 struct helem_base *hsimple;
326 snd_ctl_elem_type_t ctype;
327 long min, max;
328 int err, new = 0;
329 struct list_head *pos;
330 struct bclass_sid *bsid;
331 struct melem_sids *sid;
332 unsigned int ui;
333
334 list_for_each(pos, &priv->sids) {
335 bsid = list_entry(pos, struct bclass_sid, list);
336 for (ui = 0; ui < bsid->count; ui++) {
337 if (bsid->sids[ui].sid == sel->sid) {
338 sid = &bsid->sids[ui];
339 goto __sid_ok;
340 }
341 }
342 }
343 return 0;
344
345 __sid_ok:
346 snd_ctl_elem_info_alloca(&info);
347 err = snd_hctl_elem_info(helem, info);
348 if (err < 0)
349 return err;
350 ctype = snd_ctl_elem_info_get_type(info);
351 switch (ctype) {
352 case SND_CTL_ELEM_TYPE_ENUMERATED:
353 min = 0;
354 max = snd_ctl_elem_info_get_items(info);
355 break;
356 case SND_CTL_ELEM_TYPE_INTEGER:
357 min = snd_ctl_elem_info_get_min(info);
358 max = snd_ctl_elem_info_get_max(info);
359 break;
360 default:
361 min = max = 0;
362 break;
363 }
364
365 printf("event add: %p, %p (%s)\n", helem, sel, snd_hctl_elem_get_name(helem));
366 if (snd_mixer_selem_id_malloc(&id))
367 return -ENOMEM;
368 hsimple = calloc(1, sizeof(*hsimple));
369 if (hsimple == NULL) {
370 snd_mixer_selem_id_free(id);
371 return -ENOMEM;
372 }
373 switch (sel->purpose) {
374 case PURPOSE_SWITCH:
375 if (ctype != SND_CTL_ELEM_TYPE_BOOLEAN) {
376 __invalid_type:
377 snd_mixer_selem_id_free(id);
378 free(hsimple);
379 return -EINVAL;
380 }
381 break;
382 case PURPOSE_VOLUME:
383 if (ctype != SND_CTL_ELEM_TYPE_INTEGER)
384 goto __invalid_type;
385 break;
386 }
387 hsimple->purpose = sel->purpose;
388 hsimple->caps = sel->caps;
389 hsimple->min = min;
390 hsimple->max = max;
391 snd_mixer_selem_id_set_name(id, sid->sname);
392 snd_mixer_selem_id_set_index(id, sid->sindex);
393 melem = snd_mixer_find_selem(snd_mixer_class_get_mixer(class), id);
394 if (!melem) {
395 simple = calloc(1, sizeof(*simple));
396 if (!simple) {
397 snd_mixer_selem_id_free(id);
398 free(hsimple);
399 return -ENOMEM;
400 }
401 simple->selem.id = id;
402 simple->selem.ops = &simple_ac97_ops;
403 INIT_LIST_HEAD(&simple->helems);
404 simple->sid = sel->sid;
405 err = snd_mixer_elem_new(&melem, SND_MIXER_ELEM_SIMPLE,
406 sid->weight,
407 simple, selem_free);
408 if (err < 0) {
409 snd_mixer_selem_id_free(id);
410 free(hsimple);
411 free(simple);
412 return err;
413 }
414 new = 1;
415 } else {
416 simple = snd_mixer_elem_get_private(melem);
417 snd_mixer_selem_id_free(id);
418 }
419 list_add_tail(&hsimple->list, &simple->helems);
420 hsimple->inactive = snd_ctl_elem_info_is_inactive(info);
421 err = snd_mixer_elem_attach(melem, helem);
422 if (err < 0)
423 goto __error;
424 simple->dir[0].chanmap |= sid->chanmap[0];
425 simple->dir[1].chanmap |= sid->chanmap[1];
426 simple->selem.caps |= hsimple->caps;
427 update_ranges(simple);
428 #if 0
429 err = simple_update(melem);
430 if (err < 0) {
431 if (new)
432 goto __error;
433 return err;
434 }
435 #endif
436 if (new)
437 err = snd_mixer_elem_add(melem, class);
438 else
439 err = snd_mixer_elem_info(melem);
440 if (err < 0)
441 return err;
442 err = selem_read(melem);
443 if (err < 0)
444 return err;
445 if (err)
446 err = snd_mixer_elem_value(melem);
447 return err;
448 __error:
449 if (new)
450 snd_mixer_elem_free(melem);
451 return -EINVAL;
452 }
453
simple_event_add(snd_mixer_class_t * class,snd_hctl_elem_t * helem)454 static int simple_event_add(snd_mixer_class_t *class, snd_hctl_elem_t *helem)
455 {
456 struct bclass_private *priv = snd_mixer_sbasic_get_private(class);
457 struct bclass_selector *sel;
458 struct helem_selector *hsel;
459 struct list_head *pos;
460 snd_ctl_elem_iface_t iface = snd_hctl_elem_get_interface(helem);
461 const char *name = snd_hctl_elem_get_name(helem);
462 unsigned int index = snd_hctl_elem_get_index(helem);
463 unsigned int ui;
464 int err;
465
466 list_for_each(pos, &priv->selectors) {
467 sel = list_entry(pos, struct bclass_selector, list);
468 for (ui = 0; ui < sel->count; ui++) {
469 hsel = &sel->selectors[ui];
470 if (hsel->iface == iface && !strcmp(hsel->name, name) && hsel->index == index) {
471 err = simple_event_add1(class, helem, hsel);
472 if (err < 0)
473 return err; /* early exit? */
474 }
475 }
476 }
477 return 0;
478 }
479
alsa_mixer_sbasic_event(snd_mixer_class_t * class,unsigned int mask,snd_hctl_elem_t * helem,snd_mixer_elem_t * melem)480 int alsa_mixer_sbasic_event(snd_mixer_class_t *class, unsigned int mask,
481 snd_hctl_elem_t *helem, snd_mixer_elem_t *melem)
482 {
483 int err;
484 if (mask == SND_CTL_EVENT_MASK_REMOVE)
485 return simple_event_remove(helem, melem);
486 if (mask & SND_CTL_EVENT_MASK_ADD) {
487 err = simple_event_add(class, helem);
488 if (err < 0)
489 return err;
490 }
491 if (mask & SND_CTL_EVENT_MASK_INFO) {
492 err = simple_event_remove(helem, melem);
493 if (err < 0)
494 return err;
495 err = simple_event_add(class, helem);
496 if (err < 0)
497 return err;
498 return 0;
499 }
500 if (mask & SND_CTL_EVENT_MASK_VALUE) {
501 err = selem_read(melem);
502 if (err < 0)
503 return err;
504 if (err) {
505 err = snd_mixer_elem_value(melem);
506 if (err < 0)
507 return err;
508 }
509 }
510 return 0;
511 }
512
sbasic_cpriv_free(snd_mixer_class_t * class)513 static void sbasic_cpriv_free(snd_mixer_class_t *class)
514 {
515 struct bclass_private *priv = snd_mixer_sbasic_get_private(class);
516 struct bclass_selector *sel;
517 struct bclass_sid *sid;
518 struct list_head *pos, *pos1;
519
520 list_for_each_safe(pos, pos1, &priv->selectors) {
521 sel = list_entry(pos, struct bclass_selector, list);
522 free(sel);
523 }
524 list_for_each_safe(pos, pos1, &priv->sids) {
525 sid = list_entry(pos, struct bclass_sid, list);
526 free(sid);
527 }
528 free(priv);
529 }
530
alsa_mixer_sbasic_initpriv(snd_mixer_class_t * class,struct bclass_private * priv)531 void alsa_mixer_sbasic_initpriv(snd_mixer_class_t *class,
532 struct bclass_private *priv)
533 {
534 INIT_LIST_HEAD(&priv->selectors);
535 INIT_LIST_HEAD(&priv->sids);
536 snd_mixer_sbasic_set_private(class, priv);
537 snd_mixer_sbasic_set_private_free(class, sbasic_cpriv_free);
538 }
539
alsa_mixer_sbasic_selreg(snd_mixer_class_t * class,struct helem_selector * selectors,unsigned int count)540 int alsa_mixer_sbasic_selreg(snd_mixer_class_t *class,
541 struct helem_selector *selectors,
542 unsigned int count)
543 {
544 struct bclass_private *priv = snd_mixer_sbasic_get_private(class);
545 struct bclass_selector *sel = calloc(1, sizeof(*sel));
546
547 if (sel == NULL)
548 return -ENOMEM;
549 if (priv == NULL) {
550 priv = calloc(1, sizeof(*priv));
551 if (priv == NULL) {
552 free(sel);
553 return -ENOMEM;
554 }
555 }
556 sel->selectors = selectors;
557 sel->count = count;
558 list_add_tail(&sel->list, &priv->selectors);
559 return 0;
560 }
561
alsa_mixer_sbasic_sidreg(snd_mixer_class_t * class,struct melem_sids * sids,unsigned int count)562 int alsa_mixer_sbasic_sidreg(snd_mixer_class_t *class,
563 struct melem_sids *sids,
564 unsigned int count)
565 {
566 struct bclass_private *priv = snd_mixer_sbasic_get_private(class);
567 struct bclass_sid *sid = calloc(1, sizeof(*sid));
568
569 if (sid == NULL)
570 return -ENOMEM;
571 if (priv == NULL) {
572 priv = calloc(1, sizeof(*priv));
573 if (priv == NULL) {
574 free(sid);
575 return -ENOMEM;
576 }
577 INIT_LIST_HEAD(&priv->selectors);
578 INIT_LIST_HEAD(&priv->sids);
579 snd_mixer_sbasic_set_private(class, priv);
580 snd_mixer_sbasic_set_private_free(class, sbasic_cpriv_free);
581 }
582 sid->sids = sids;
583 sid->count = count;
584 list_add(&sid->list, &priv->sids);
585 return 0;
586 }
587