1 /* 2 * Mixer Interface - simple abstact module - base library 3 * Copyright (c) 2005 by Jaroslav Kysela <perex (at) 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 288 static int selem_read(snd_mixer_elem_t *elem) 289 { 290 printf("elem read: %p\n", elem); 291 return 0; 292 } 293 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 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 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 unsigned long values; 328 long min, max; 329 int err, new = 0; 330 struct list_head *pos; 331 struct bclass_sid *bsid; 332 struct melem_sids *sid; 333 unsigned int ui; 334 335 list_for_each(pos, &priv->sids) { 336 bsid = list_entry(pos, struct bclass_sid, list); 337 for (ui = 0; ui < bsid->count; ui++) { 338 if (bsid->sids[ui].sid == sel->sid) { 339 sid = &bsid->sids[ui]; 340 goto __sid_ok; 341 } 342 } 343 } 344 return 0; 345 346 __sid_ok: 347 snd_ctl_elem_info_alloca(&info); 348 err = snd_hctl_elem_info(helem, info); 349 if (err < 0) 350 return err; 351 ctype = snd_ctl_elem_info_get_type(info); 352 values = snd_ctl_elem_info_get_count(info); 353 switch (ctype) { 354 case SND_CTL_ELEM_TYPE_ENUMERATED: 355 min = 0; 356 max = snd_ctl_elem_info_get_items(info); 357 break; 358 case SND_CTL_ELEM_TYPE_INTEGER: 359 min = snd_ctl_elem_info_get_min(info); 360 max = snd_ctl_elem_info_get_max(info); 361 break; 362 default: 363 min = max = 0; 364 break; 365 } 366 367 printf("event add: %p, %p (%s)\n", helem, sel, snd_hctl_elem_get_name(helem)); 368 if (snd_mixer_selem_id_malloc(&id)) 369 return -ENOMEM; 370 hsimple = calloc(1, sizeof(*hsimple)); 371 if (hsimple == NULL) { 372 snd_mixer_selem_id_free(id); 373 return -ENOMEM; 374 } 375 switch (sel->purpose) { 376 case PURPOSE_SWITCH: 377 if (ctype != SND_CTL_ELEM_TYPE_BOOLEAN) { 378 __invalid_type: 379 snd_mixer_selem_id_free(id); 380 return -EINVAL; 381 } 382 break; 383 case PURPOSE_VOLUME: 384 if (ctype != SND_CTL_ELEM_TYPE_INTEGER) 385 goto __invalid_type; 386 break; 387 } 388 hsimple->purpose = sel->purpose; 389 hsimple->caps = sel->caps; 390 hsimple->min = min; 391 hsimple->max = max; 392 snd_mixer_selem_id_set_name(id, sid->sname); 393 snd_mixer_selem_id_set_index(id, sid->sindex); 394 melem = snd_mixer_find_selem(snd_mixer_class_get_mixer(class), id); 395 if (!melem) { 396 simple = calloc(1, sizeof(*simple)); 397 if (!simple) { 398 snd_mixer_selem_id_free(id); 399 free(hsimple); 400 return -ENOMEM; 401 } 402 simple->selem.id = id; 403 simple->selem.ops = &simple_ac97_ops; 404 INIT_LIST_HEAD(&simple->helems); 405 simple->sid = sel->sid; 406 err = snd_mixer_elem_new(&melem, SND_MIXER_ELEM_SIMPLE, 407 sid->weight, 408 simple, selem_free); 409 if (err < 0) { 410 snd_mixer_selem_id_free(id); 411 free(hsimple); 412 free(simple); 413 return err; 414 } 415 new = 1; 416 } else { 417 simple = snd_mixer_elem_get_private(melem); 418 snd_mixer_selem_id_free(id); 419 } 420 list_add_tail(&hsimple->list, &simple->helems); 421 hsimple->inactive = snd_ctl_elem_info_is_inactive(info); 422 err = snd_mixer_elem_attach(melem, helem); 423 if (err < 0) 424 goto __error; 425 simple->dir[0].chanmap |= sid->chanmap[0]; 426 simple->dir[1].chanmap |= sid->chanmap[1]; 427 simple->selem.caps |= hsimple->caps; 428 update_ranges(simple); 429 #if 0 430 err = simple_update(melem); 431 if (err < 0) { 432 if (new) 433 goto __error; 434 return err; 435 } 436 #endif 437 if (new) 438 err = snd_mixer_elem_add(melem, class); 439 else 440 err = snd_mixer_elem_info(melem); 441 if (err < 0) 442 return err; 443 err = selem_read(melem); 444 if (err < 0) 445 return err; 446 if (err) 447 err = snd_mixer_elem_value(melem); 448 return err; 449 __error: 450 if (new) 451 snd_mixer_elem_free(melem); 452 return -EINVAL; 453 } 454 455 static int simple_event_add(snd_mixer_class_t *class, snd_hctl_elem_t *helem) 456 { 457 struct bclass_private *priv = snd_mixer_sbasic_get_private(class); 458 struct bclass_selector *sel; 459 struct helem_selector *hsel; 460 struct list_head *pos; 461 snd_ctl_elem_iface_t iface = snd_hctl_elem_get_interface(helem); 462 const char *name = snd_hctl_elem_get_name(helem); 463 unsigned int index = snd_hctl_elem_get_index(helem); 464 unsigned int ui; 465 int err; 466 467 list_for_each(pos, &priv->selectors) { 468 sel = list_entry(pos, struct bclass_selector, list); 469 for (ui = 0; ui < sel->count; ui++) { 470 hsel = &sel->selectors[ui]; 471 if (hsel->iface == iface && !strcmp(hsel->name, name) && hsel->index == index) { 472 err = simple_event_add1(class, helem, hsel); 473 if (err < 0) 474 return err; /* early exit? */ 475 } 476 } 477 } 478 return 0; 479 } 480 481 int alsa_mixer_sbasic_event(snd_mixer_class_t *class, unsigned int mask, 482 snd_hctl_elem_t *helem, snd_mixer_elem_t *melem) 483 { 484 int err; 485 if (mask == SND_CTL_EVENT_MASK_REMOVE) 486 return simple_event_remove(helem, melem); 487 if (mask & SND_CTL_EVENT_MASK_ADD) { 488 err = simple_event_add(class, helem); 489 if (err < 0) 490 return err; 491 } 492 if (mask & SND_CTL_EVENT_MASK_INFO) { 493 err = simple_event_remove(helem, melem); 494 if (err < 0) 495 return err; 496 err = simple_event_add(class, helem); 497 if (err < 0) 498 return err; 499 return 0; 500 } 501 if (mask & SND_CTL_EVENT_MASK_VALUE) { 502 err = selem_read(melem); 503 if (err < 0) 504 return err; 505 if (err) { 506 err = snd_mixer_elem_value(melem); 507 if (err < 0) 508 return err; 509 } 510 } 511 return 0; 512 } 513 514 static void sbasic_cpriv_free(snd_mixer_class_t *class) 515 { 516 struct bclass_private *priv = snd_mixer_sbasic_get_private(class); 517 struct bclass_selector *sel; 518 struct bclass_sid *sid; 519 struct list_head *pos, *pos1; 520 521 list_for_each_safe(pos, pos1, &priv->selectors) { 522 sel = list_entry(pos, struct bclass_selector, list); 523 free(sel); 524 } 525 list_for_each_safe(pos, pos1, &priv->sids) { 526 sid = list_entry(pos, struct bclass_sid, list); 527 free(sid); 528 } 529 free(priv); 530 } 531 532 void alsa_mixer_sbasic_initpriv(snd_mixer_class_t *class, 533 struct bclass_private *priv) 534 { 535 INIT_LIST_HEAD(&priv->selectors); 536 INIT_LIST_HEAD(&priv->sids); 537 snd_mixer_sbasic_set_private(class, priv); 538 snd_mixer_sbasic_set_private_free(class, sbasic_cpriv_free); 539 } 540 541 int alsa_mixer_sbasic_selreg(snd_mixer_class_t *class, 542 struct helem_selector *selectors, 543 unsigned int count) 544 { 545 struct bclass_private *priv = snd_mixer_sbasic_get_private(class); 546 struct bclass_selector *sel = calloc(1, sizeof(*sel)); 547 548 if (sel == NULL) 549 return -ENOMEM; 550 if (priv == NULL) { 551 priv = calloc(1, sizeof(*priv)); 552 if (priv == NULL) { 553 free(sel); 554 return -ENOMEM; 555 } 556 } 557 sel->selectors = selectors; 558 sel->count = count; 559 list_add_tail(&sel->list, &priv->selectors); 560 return 0; 561 } 562 563 int alsa_mixer_sbasic_sidreg(snd_mixer_class_t *class, 564 struct melem_sids *sids, 565 unsigned int count) 566 { 567 struct bclass_private *priv = snd_mixer_sbasic_get_private(class); 568 struct bclass_sid *sid = calloc(1, sizeof(*sid)); 569 570 if (sid == NULL) 571 return -ENOMEM; 572 if (priv == NULL) { 573 priv = calloc(1, sizeof(*priv)); 574 if (priv == NULL) { 575 free(sid); 576 return -ENOMEM; 577 } 578 INIT_LIST_HEAD(&priv->selectors); 579 INIT_LIST_HEAD(&priv->sids); 580 snd_mixer_sbasic_set_private(class, priv); 581 snd_mixer_sbasic_set_private_free(class, sbasic_cpriv_free); 582 } 583 sid->sids = sids; 584 sid->count = count; 585 list_add(&sid->list, &priv->sids); 586 return 0; 587 } 588