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 37static int selem_read(snd_mixer_elem_t *elem); 38 39/* 40 * Helpers 41 */ 42 43static 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 54static 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 63static 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 73static 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 112static 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 154static 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 165static 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 173static 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 188static 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 197static 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 205static 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 215static 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 224static 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 233static 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 243static 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 252static 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 260static 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 268static 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 288static int selem_read(snd_mixer_elem_t *elem) 289{ 290 printf("elem read: %p\n", elem); 291 return 0; 292} 293 294static 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 301static 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 316static 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 454static 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 480int 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 513static 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 531void 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 540int 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 562int 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