784 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * LED state routines for driver control interface4 * Copyright (c) 2021 by Jaroslav Kysela <perex@perex.cz>5 */6 7#include <linux/slab.h>8#include <linux/module.h>9#include <linux/leds.h>10#include <sound/core.h>11#include <sound/control.h>12 13MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");14MODULE_DESCRIPTION("ALSA control interface to LED trigger code.");15MODULE_LICENSE("GPL");16 17#define MAX_LED (((SNDRV_CTL_ELEM_ACCESS_MIC_LED - SNDRV_CTL_ELEM_ACCESS_SPK_LED) \18 >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) + 1)19 20#define to_led_card_dev(_dev) \21 container_of(_dev, struct snd_ctl_led_card, dev)22 23enum snd_ctl_led_mode {24 MODE_FOLLOW_MUTE = 0,25 MODE_FOLLOW_ROUTE,26 MODE_OFF,27 MODE_ON,28};29 30struct snd_ctl_led_card {31 struct device dev;32 int number;33 struct snd_ctl_led *led;34};35 36struct snd_ctl_led {37 struct device dev;38 struct list_head controls;39 const char *name;40 unsigned int group;41 enum led_audio trigger_type;42 enum snd_ctl_led_mode mode;43 struct snd_ctl_led_card *cards[SNDRV_CARDS];44};45 46struct snd_ctl_led_ctl {47 struct list_head list;48 struct snd_card *card;49 unsigned int access;50 struct snd_kcontrol *kctl;51 unsigned int index_offset;52};53 54static DEFINE_MUTEX(snd_ctl_led_mutex);55static bool snd_ctl_led_card_valid[SNDRV_CARDS];56static struct led_trigger *snd_ctl_ledtrig_audio[NUM_AUDIO_LEDS];57static struct snd_ctl_led snd_ctl_leds[MAX_LED] = {58 {59 .name = "speaker",60 .group = (SNDRV_CTL_ELEM_ACCESS_SPK_LED >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1,61 .trigger_type = LED_AUDIO_MUTE,62 .mode = MODE_FOLLOW_MUTE,63 },64 {65 .name = "mic",66 .group = (SNDRV_CTL_ELEM_ACCESS_MIC_LED >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1,67 .trigger_type = LED_AUDIO_MICMUTE,68 .mode = MODE_FOLLOW_MUTE,69 },70};71 72static void snd_ctl_led_sysfs_add(struct snd_card *card);73static void snd_ctl_led_sysfs_remove(struct snd_card *card);74 75#define UPDATE_ROUTE(route, cb) \76 do { \77 int route2 = (cb); \78 if (route2 >= 0) \79 route = route < 0 ? route2 : (route | route2); \80 } while (0)81 82static inline unsigned int access_to_group(unsigned int access)83{84 return ((access & SNDRV_CTL_ELEM_ACCESS_LED_MASK) >>85 SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1;86}87 88static inline unsigned int group_to_access(unsigned int group)89{90 return (group + 1) << SNDRV_CTL_ELEM_ACCESS_LED_SHIFT;91}92 93static struct snd_ctl_led *snd_ctl_led_get_by_access(unsigned int access)94{95 unsigned int group = access_to_group(access);96 if (group >= MAX_LED)97 return NULL;98 return &snd_ctl_leds[group];99}100 101/*102 * A note for callers:103 * The two static variables info and value are protected using snd_ctl_led_mutex.104 */105static int snd_ctl_led_get(struct snd_ctl_led_ctl *lctl)106{107 static struct snd_ctl_elem_info info;108 static struct snd_ctl_elem_value value;109 struct snd_kcontrol *kctl = lctl->kctl;110 unsigned int i;111 int result;112 113 memset(&info, 0, sizeof(info));114 info.id = kctl->id;115 info.id.index += lctl->index_offset;116 info.id.numid += lctl->index_offset;117 result = kctl->info(kctl, &info);118 if (result < 0)119 return -1;120 memset(&value, 0, sizeof(value));121 value.id = info.id;122 result = kctl->get(kctl, &value);123 if (result < 0)124 return -1;125 if (info.type == SNDRV_CTL_ELEM_TYPE_BOOLEAN ||126 info.type == SNDRV_CTL_ELEM_TYPE_INTEGER) {127 for (i = 0; i < info.count; i++)128 if (value.value.integer.value[i] != info.value.integer.min)129 return 1;130 } else if (info.type == SNDRV_CTL_ELEM_TYPE_INTEGER64) {131 for (i = 0; i < info.count; i++)132 if (value.value.integer64.value[i] != info.value.integer64.min)133 return 1;134 }135 return 0;136}137 138static void snd_ctl_led_set_state(struct snd_card *card, unsigned int access,139 struct snd_kcontrol *kctl, unsigned int ioff)140{141 struct snd_ctl_led *led;142 struct snd_ctl_led_ctl *lctl;143 int route;144 bool found;145 146 led = snd_ctl_led_get_by_access(access);147 if (!led)148 return;149 route = -1;150 found = false;151 scoped_guard(mutex, &snd_ctl_led_mutex) {152 /* the card may not be registered (active) at this point */153 if (card && !snd_ctl_led_card_valid[card->number])154 return;155 list_for_each_entry(lctl, &led->controls, list) {156 if (lctl->kctl == kctl && lctl->index_offset == ioff)157 found = true;158 UPDATE_ROUTE(route, snd_ctl_led_get(lctl));159 }160 if (!found && kctl && card) {161 lctl = kzalloc(sizeof(*lctl), GFP_KERNEL);162 if (lctl) {163 lctl->card = card;164 lctl->access = access;165 lctl->kctl = kctl;166 lctl->index_offset = ioff;167 list_add(&lctl->list, &led->controls);168 UPDATE_ROUTE(route, snd_ctl_led_get(lctl));169 }170 }171 }172 switch (led->mode) {173 case MODE_OFF: route = 1; break;174 case MODE_ON: route = 0; break;175 case MODE_FOLLOW_ROUTE: if (route >= 0) route ^= 1; break;176 case MODE_FOLLOW_MUTE: /* noop */ break;177 }178 if (route >= 0) {179 struct led_trigger *trig = snd_ctl_ledtrig_audio[led->trigger_type];180 181 led_trigger_event(trig, route ? LED_OFF : LED_ON);182 }183}184 185static struct snd_ctl_led_ctl *snd_ctl_led_find(struct snd_kcontrol *kctl, unsigned int ioff)186{187 struct list_head *controls;188 struct snd_ctl_led_ctl *lctl;189 unsigned int group;190 191 for (group = 0; group < MAX_LED; group++) {192 controls = &snd_ctl_leds[group].controls;193 list_for_each_entry(lctl, controls, list)194 if (lctl->kctl == kctl && lctl->index_offset == ioff)195 return lctl;196 }197 return NULL;198}199 200static unsigned int snd_ctl_led_remove(struct snd_kcontrol *kctl, unsigned int ioff,201 unsigned int access)202{203 struct snd_ctl_led_ctl *lctl;204 unsigned int ret = 0;205 206 guard(mutex)(&snd_ctl_led_mutex);207 lctl = snd_ctl_led_find(kctl, ioff);208 if (lctl && (access == 0 || access != lctl->access)) {209 ret = lctl->access;210 list_del(&lctl->list);211 kfree(lctl);212 }213 return ret;214}215 216static void snd_ctl_led_notify(struct snd_card *card, unsigned int mask,217 struct snd_kcontrol *kctl, unsigned int ioff)218{219 struct snd_kcontrol_volatile *vd;220 unsigned int access, access2;221 222 if (mask == SNDRV_CTL_EVENT_MASK_REMOVE) {223 access = snd_ctl_led_remove(kctl, ioff, 0);224 if (access)225 snd_ctl_led_set_state(card, access, NULL, 0);226 } else if (mask & SNDRV_CTL_EVENT_MASK_INFO) {227 vd = &kctl->vd[ioff];228 access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK;229 access2 = snd_ctl_led_remove(kctl, ioff, access);230 if (access2)231 snd_ctl_led_set_state(card, access2, NULL, 0);232 if (access)233 snd_ctl_led_set_state(card, access, kctl, ioff);234 } else if ((mask & (SNDRV_CTL_EVENT_MASK_ADD |235 SNDRV_CTL_EVENT_MASK_VALUE)) != 0) {236 vd = &kctl->vd[ioff];237 access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK;238 if (access)239 snd_ctl_led_set_state(card, access, kctl, ioff);240 }241}242 243DEFINE_FREE(snd_card_unref, struct snd_card *, if (_T) snd_card_unref(_T))244 245static int snd_ctl_led_set_id(int card_number, struct snd_ctl_elem_id *id,246 unsigned int group, bool set)247{248 struct snd_card *card __free(snd_card_unref) = NULL;249 struct snd_kcontrol *kctl;250 struct snd_kcontrol_volatile *vd;251 unsigned int ioff, access, new_access;252 253 card = snd_card_ref(card_number);254 if (!card)255 return -ENXIO;256 guard(rwsem_write)(&card->controls_rwsem);257 kctl = snd_ctl_find_id(card, id);258 if (!kctl)259 return -ENOENT;260 ioff = snd_ctl_get_ioff(kctl, id);261 vd = &kctl->vd[ioff];262 access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK;263 if (access != 0 && access != group_to_access(group))264 return -EXDEV;265 new_access = vd->access & ~SNDRV_CTL_ELEM_ACCESS_LED_MASK;266 if (set)267 new_access |= group_to_access(group);268 if (new_access != vd->access) {269 vd->access = new_access;270 snd_ctl_led_notify(card, SNDRV_CTL_EVENT_MASK_INFO, kctl, ioff);271 }272 return 0;273}274 275static void snd_ctl_led_refresh(void)276{277 unsigned int group;278 279 for (group = 0; group < MAX_LED; group++)280 snd_ctl_led_set_state(NULL, group_to_access(group), NULL, 0);281}282 283static void snd_ctl_led_ctl_destroy(struct snd_ctl_led_ctl *lctl)284{285 list_del(&lctl->list);286 kfree(lctl);287}288 289static void snd_ctl_led_clean(struct snd_card *card)290{291 unsigned int group;292 struct snd_ctl_led_ctl *lctl, *_lctl;293 struct snd_ctl_led *led;294 295 for (group = 0; group < MAX_LED; group++) {296 led = &snd_ctl_leds[group];297 list_for_each_entry_safe(lctl, _lctl, &led->controls, list)298 if (!card || lctl->card == card)299 snd_ctl_led_ctl_destroy(lctl);300 }301}302 303static int snd_ctl_led_reset(int card_number, unsigned int group)304{305 struct snd_card *card __free(snd_card_unref) = NULL;306 struct snd_ctl_led_ctl *lctl, *_lctl;307 struct snd_ctl_led *led;308 struct snd_kcontrol_volatile *vd;309 bool change = false;310 311 card = snd_card_ref(card_number);312 if (!card)313 return -ENXIO;314 315 scoped_guard(mutex, &snd_ctl_led_mutex) {316 if (!snd_ctl_led_card_valid[card_number])317 return -ENXIO;318 led = &snd_ctl_leds[group];319 list_for_each_entry_safe(lctl, _lctl, &led->controls, list)320 if (lctl->card == card) {321 vd = &lctl->kctl->vd[lctl->index_offset];322 vd->access &= ~group_to_access(group);323 snd_ctl_led_ctl_destroy(lctl);324 change = true;325 }326 }327 if (change)328 snd_ctl_led_set_state(NULL, group_to_access(group), NULL, 0);329 return 0;330}331 332static void snd_ctl_led_register(struct snd_card *card)333{334 struct snd_kcontrol *kctl;335 unsigned int ioff;336 337 if (snd_BUG_ON(card->number < 0 ||338 card->number >= ARRAY_SIZE(snd_ctl_led_card_valid)))339 return;340 scoped_guard(mutex, &snd_ctl_led_mutex)341 snd_ctl_led_card_valid[card->number] = true;342 /* the register callback is already called with held card->controls_rwsem */343 list_for_each_entry(kctl, &card->controls, list)344 for (ioff = 0; ioff < kctl->count; ioff++)345 snd_ctl_led_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, kctl, ioff);346 snd_ctl_led_refresh();347 snd_ctl_led_sysfs_add(card);348}349 350static void snd_ctl_led_disconnect(struct snd_card *card)351{352 snd_ctl_led_sysfs_remove(card);353 scoped_guard(mutex, &snd_ctl_led_mutex) {354 snd_ctl_led_card_valid[card->number] = false;355 snd_ctl_led_clean(card);356 }357 snd_ctl_led_refresh();358}359 360static void snd_ctl_led_card_release(struct device *dev)361{362 struct snd_ctl_led_card *led_card = to_led_card_dev(dev);363 364 kfree(led_card);365}366 367static void snd_ctl_led_release(struct device *dev)368{369}370 371static void snd_ctl_led_dev_release(struct device *dev)372{373}374 375/*376 * sysfs377 */378 379static ssize_t mode_show(struct device *dev,380 struct device_attribute *attr, char *buf)381{382 struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev);383 const char *str = NULL;384 385 switch (led->mode) {386 case MODE_FOLLOW_MUTE: str = "follow-mute"; break;387 case MODE_FOLLOW_ROUTE: str = "follow-route"; break;388 case MODE_ON: str = "on"; break;389 case MODE_OFF: str = "off"; break;390 }391 return sysfs_emit(buf, "%s\n", str);392}393 394static ssize_t mode_store(struct device *dev,395 struct device_attribute *attr,396 const char *buf, size_t count)397{398 struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev);399 char _buf[16];400 size_t l = min(count, sizeof(_buf) - 1);401 enum snd_ctl_led_mode mode;402 403 memcpy(_buf, buf, l);404 _buf[l] = '\0';405 if (strstr(_buf, "mute"))406 mode = MODE_FOLLOW_MUTE;407 else if (strstr(_buf, "route"))408 mode = MODE_FOLLOW_ROUTE;409 else if (strncmp(_buf, "off", 3) == 0 || strncmp(_buf, "0", 1) == 0)410 mode = MODE_OFF;411 else if (strncmp(_buf, "on", 2) == 0 || strncmp(_buf, "1", 1) == 0)412 mode = MODE_ON;413 else414 return count;415 416 scoped_guard(mutex, &snd_ctl_led_mutex)417 led->mode = mode;418 419 snd_ctl_led_set_state(NULL, group_to_access(led->group), NULL, 0);420 return count;421}422 423static ssize_t brightness_show(struct device *dev,424 struct device_attribute *attr, char *buf)425{426 struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev);427 struct led_trigger *trig = snd_ctl_ledtrig_audio[led->trigger_type];428 429 return sysfs_emit(buf, "%u\n", led_trigger_get_brightness(trig));430}431 432static DEVICE_ATTR_RW(mode);433static DEVICE_ATTR_RO(brightness);434 435static struct attribute *snd_ctl_led_dev_attrs[] = {436 &dev_attr_mode.attr,437 &dev_attr_brightness.attr,438 NULL,439};440 441static const struct attribute_group snd_ctl_led_dev_attr_group = {442 .attrs = snd_ctl_led_dev_attrs,443};444 445static const struct attribute_group *snd_ctl_led_dev_attr_groups[] = {446 &snd_ctl_led_dev_attr_group,447 NULL,448};449 450static char *find_eos(char *s)451{452 while (*s && *s != ',')453 s++;454 if (*s)455 s++;456 return s;457}458 459static char *parse_uint(char *s, unsigned int *val)460{461 unsigned long long res;462 if (kstrtoull(s, 10, &res))463 res = 0;464 *val = res;465 return find_eos(s);466}467 468static char *parse_string(char *s, char *val, size_t val_size)469{470 if (*s == '"' || *s == '\'') {471 char c = *s;472 s++;473 while (*s && *s != c) {474 if (val_size > 1) {475 *val++ = *s;476 val_size--;477 }478 s++;479 }480 } else {481 while (*s && *s != ',') {482 if (val_size > 1) {483 *val++ = *s;484 val_size--;485 }486 s++;487 }488 }489 *val = '\0';490 if (*s)491 s++;492 return s;493}494 495static char *parse_iface(char *s, snd_ctl_elem_iface_t *val)496{497 if (!strncasecmp(s, "card", 4))498 *val = SNDRV_CTL_ELEM_IFACE_CARD;499 else if (!strncasecmp(s, "mixer", 5))500 *val = SNDRV_CTL_ELEM_IFACE_MIXER;501 return find_eos(s);502}503 504/*505 * These types of input strings are accepted:506 *507 * unsigned integer - numid (equivaled to numid=UINT)508 * string - basic mixer name (equivalent to iface=MIXER,name=STR)509 * numid=UINT510 * [iface=MIXER,][device=UINT,][subdevice=UINT,]name=STR[,index=UINT]511 */512static ssize_t set_led_id(struct snd_ctl_led_card *led_card, const char *buf, size_t count,513 bool attach)514{515 char buf2[256], *s, *os;516 struct snd_ctl_elem_id id;517 int err;518 519 if (strscpy(buf2, buf, sizeof(buf2)) < 0)520 return -E2BIG;521 memset(&id, 0, sizeof(id));522 id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;523 s = buf2;524 while (*s) {525 os = s;526 if (!strncasecmp(s, "numid=", 6)) {527 s = parse_uint(s + 6, &id.numid);528 } else if (!strncasecmp(s, "iface=", 6)) {529 s = parse_iface(s + 6, &id.iface);530 } else if (!strncasecmp(s, "device=", 7)) {531 s = parse_uint(s + 7, &id.device);532 } else if (!strncasecmp(s, "subdevice=", 10)) {533 s = parse_uint(s + 10, &id.subdevice);534 } else if (!strncasecmp(s, "name=", 5)) {535 s = parse_string(s + 5, id.name, sizeof(id.name));536 } else if (!strncasecmp(s, "index=", 6)) {537 s = parse_uint(s + 6, &id.index);538 } else if (s == buf2) {539 while (*s) {540 if (*s < '0' || *s > '9')541 break;542 s++;543 }544 if (*s == '\0')545 parse_uint(buf2, &id.numid);546 else {547 for (; *s >= ' '; s++);548 *s = '\0';549 strscpy(id.name, buf2, sizeof(id.name));550 }551 break;552 }553 if (*s == ',')554 s++;555 if (s == os)556 break;557 }558 559 err = snd_ctl_led_set_id(led_card->number, &id, led_card->led->group, attach);560 if (err < 0)561 return err;562 563 return count;564}565 566static ssize_t attach_store(struct device *dev,567 struct device_attribute *attr,568 const char *buf, size_t count)569{570 struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev);571 return set_led_id(led_card, buf, count, true);572}573 574static ssize_t detach_store(struct device *dev,575 struct device_attribute *attr,576 const char *buf, size_t count)577{578 struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev);579 return set_led_id(led_card, buf, count, false);580}581 582static ssize_t reset_store(struct device *dev,583 struct device_attribute *attr,584 const char *buf, size_t count)585{586 struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev);587 int err;588 589 if (count > 0 && buf[0] == '1') {590 err = snd_ctl_led_reset(led_card->number, led_card->led->group);591 if (err < 0)592 return err;593 }594 return count;595}596 597static ssize_t list_show(struct device *dev,598 struct device_attribute *attr, char *buf)599{600 struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev);601 struct snd_card *card __free(snd_card_unref) = NULL;602 struct snd_ctl_led_ctl *lctl;603 size_t l = 0;604 605 card = snd_card_ref(led_card->number);606 if (!card)607 return -ENXIO;608 guard(rwsem_read)(&card->controls_rwsem);609 guard(mutex)(&snd_ctl_led_mutex);610 if (snd_ctl_led_card_valid[led_card->number]) {611 list_for_each_entry(lctl, &led_card->led->controls, list) {612 if (lctl->card != card)613 continue;614 if (l)615 l += sysfs_emit_at(buf, l, " ");616 l += sysfs_emit_at(buf, l, "%u",617 lctl->kctl->id.numid + lctl->index_offset);618 }619 }620 return l;621}622 623static DEVICE_ATTR_WO(attach);624static DEVICE_ATTR_WO(detach);625static DEVICE_ATTR_WO(reset);626static DEVICE_ATTR_RO(list);627 628static struct attribute *snd_ctl_led_card_attrs[] = {629 &dev_attr_attach.attr,630 &dev_attr_detach.attr,631 &dev_attr_reset.attr,632 &dev_attr_list.attr,633 NULL,634};635 636static const struct attribute_group snd_ctl_led_card_attr_group = {637 .attrs = snd_ctl_led_card_attrs,638};639 640static const struct attribute_group *snd_ctl_led_card_attr_groups[] = {641 &snd_ctl_led_card_attr_group,642 NULL,643};644 645static struct device snd_ctl_led_dev;646 647static void snd_ctl_led_sysfs_add(struct snd_card *card)648{649 unsigned int group;650 struct snd_ctl_led_card *led_card;651 struct snd_ctl_led *led;652 char link_name[32];653 654 for (group = 0; group < MAX_LED; group++) {655 led = &snd_ctl_leds[group];656 led_card = kzalloc(sizeof(*led_card), GFP_KERNEL);657 if (!led_card)658 goto cerr2;659 led_card->number = card->number;660 led_card->led = led;661 device_initialize(&led_card->dev);662 led_card->dev.release = snd_ctl_led_card_release;663 if (dev_set_name(&led_card->dev, "card%d", card->number) < 0)664 goto cerr;665 led_card->dev.parent = &led->dev;666 led_card->dev.groups = snd_ctl_led_card_attr_groups;667 if (device_add(&led_card->dev))668 goto cerr;669 led->cards[card->number] = led_card;670 snprintf(link_name, sizeof(link_name), "led-%s", led->name);671 WARN(sysfs_create_link(&card->ctl_dev->kobj, &led_card->dev.kobj, link_name),672 "can't create symlink to controlC%i device\n", card->number);673 WARN(sysfs_create_link(&led_card->dev.kobj, &card->card_dev.kobj, "card"),674 "can't create symlink to card%i\n", card->number);675 676 continue;677cerr:678 put_device(&led_card->dev);679cerr2:680 dev_err(card->dev, "snd_ctl_led: unable to add card%d", card->number);681 }682}683 684static void snd_ctl_led_sysfs_remove(struct snd_card *card)685{686 unsigned int group;687 struct snd_ctl_led_card *led_card;688 struct snd_ctl_led *led;689 char link_name[32];690 691 for (group = 0; group < MAX_LED; group++) {692 led = &snd_ctl_leds[group];693 led_card = led->cards[card->number];694 if (!led_card)695 continue;696 snprintf(link_name, sizeof(link_name), "led-%s", led->name);697 sysfs_remove_link(&card->ctl_dev->kobj, link_name);698 sysfs_remove_link(&led_card->dev.kobj, "card");699 device_unregister(&led_card->dev);700 led->cards[card->number] = NULL;701 }702}703 704/*705 * Control layer registration706 */707static struct snd_ctl_layer_ops snd_ctl_led_lops = {708 .module_name = SND_CTL_LAYER_MODULE_LED,709 .lregister = snd_ctl_led_register,710 .ldisconnect = snd_ctl_led_disconnect,711 .lnotify = snd_ctl_led_notify,712};713 714static int __init snd_ctl_led_init(void)715{716 struct snd_ctl_led *led;717 unsigned int group;718 719 led_trigger_register_simple("audio-mute", &snd_ctl_ledtrig_audio[LED_AUDIO_MUTE]);720 led_trigger_register_simple("audio-micmute", &snd_ctl_ledtrig_audio[LED_AUDIO_MICMUTE]);721 722 device_initialize(&snd_ctl_led_dev);723 snd_ctl_led_dev.class = &sound_class;724 snd_ctl_led_dev.release = snd_ctl_led_dev_release;725 dev_set_name(&snd_ctl_led_dev, "ctl-led");726 if (device_add(&snd_ctl_led_dev)) {727 put_device(&snd_ctl_led_dev);728 return -ENOMEM;729 }730 for (group = 0; group < MAX_LED; group++) {731 led = &snd_ctl_leds[group];732 INIT_LIST_HEAD(&led->controls);733 device_initialize(&led->dev);734 led->dev.parent = &snd_ctl_led_dev;735 led->dev.release = snd_ctl_led_release;736 led->dev.groups = snd_ctl_led_dev_attr_groups;737 dev_set_name(&led->dev, led->name);738 if (device_add(&led->dev)) {739 put_device(&led->dev);740 for (; group > 0; group--) {741 led = &snd_ctl_leds[group - 1];742 device_unregister(&led->dev);743 }744 device_unregister(&snd_ctl_led_dev);745 return -ENOMEM;746 }747 }748 snd_ctl_register_layer(&snd_ctl_led_lops);749 return 0;750}751 752static void __exit snd_ctl_led_exit(void)753{754 struct snd_ctl_led *led;755 struct snd_card *card;756 unsigned int group, card_number;757 758 snd_ctl_disconnect_layer(&snd_ctl_led_lops);759 for (card_number = 0; card_number < SNDRV_CARDS; card_number++) {760 if (!snd_ctl_led_card_valid[card_number])761 continue;762 card = snd_card_ref(card_number);763 if (card) {764 snd_ctl_led_sysfs_remove(card);765 snd_card_unref(card);766 }767 }768 for (group = 0; group < MAX_LED; group++) {769 led = &snd_ctl_leds[group];770 device_unregister(&led->dev);771 }772 device_unregister(&snd_ctl_led_dev);773 snd_ctl_led_clean(NULL);774 775 led_trigger_unregister_simple(snd_ctl_ledtrig_audio[LED_AUDIO_MUTE]);776 led_trigger_unregister_simple(snd_ctl_ledtrig_audio[LED_AUDIO_MICMUTE]);777}778 779module_init(snd_ctl_led_init)780module_exit(snd_ctl_led_exit)781 782MODULE_ALIAS("ledtrig:audio-mute");783MODULE_ALIAS("ledtrig:audio-micmute");784