1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * LED state routines for driver control interface
4 * Copyright (c) 2021 by Jaroslav Kysela <perex@perex.cz>
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>
13 MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
14 MODULE_DESCRIPTION("ALSA control interface to LED trigger code.");
15 MODULE_LICENSE("GPL");
17 #define MAX_LED (((SNDRV_CTL_ELEM_ACCESS_MIC_LED - SNDRV_CTL_ELEM_ACCESS_SPK_LED) \
18 >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) + 1)
20 #define to_led_card_dev(_dev) \
21 container_of(_dev, struct snd_ctl_led_card, dev)
23 enum snd_ctl_led_mode
{
30 struct snd_ctl_led_card
{
33 struct snd_ctl_led
*led
;
38 struct list_head controls
;
41 enum led_audio trigger_type
;
42 enum snd_ctl_led_mode mode
;
43 struct snd_ctl_led_card
*cards
[SNDRV_CARDS
];
46 struct snd_ctl_led_ctl
{
47 struct list_head list
;
48 struct snd_card
*card
;
50 struct snd_kcontrol
*kctl
;
51 unsigned int index_offset
;
54 static DEFINE_MUTEX(snd_ctl_led_mutex
);
55 static bool snd_ctl_led_card_valid
[SNDRV_CARDS
];
56 static struct led_trigger
*snd_ctl_ledtrig_audio
[NUM_AUDIO_LEDS
];
57 static struct snd_ctl_led snd_ctl_leds
[MAX_LED
] = {
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
,
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
,
72 static void snd_ctl_led_sysfs_add(struct snd_card
*card
);
73 static void snd_ctl_led_sysfs_remove(struct snd_card
*card
);
75 #define UPDATE_ROUTE(route, cb) \
79 route = route < 0 ? route2 : (route | route2); \
82 static inline unsigned int access_to_group(unsigned int access
)
84 return ((access
& SNDRV_CTL_ELEM_ACCESS_LED_MASK
) >>
85 SNDRV_CTL_ELEM_ACCESS_LED_SHIFT
) - 1;
88 static inline unsigned int group_to_access(unsigned int group
)
90 return (group
+ 1) << SNDRV_CTL_ELEM_ACCESS_LED_SHIFT
;
93 static struct snd_ctl_led
*snd_ctl_led_get_by_access(unsigned int access
)
95 unsigned int group
= access_to_group(access
);
98 return &snd_ctl_leds
[group
];
102 * A note for callers:
103 * The two static variables info and value are protected using snd_ctl_led_mutex.
105 static int snd_ctl_led_get(struct snd_ctl_led_ctl
*lctl
)
107 static struct snd_ctl_elem_info info
;
108 static struct snd_ctl_elem_value value
;
109 struct snd_kcontrol
*kctl
= lctl
->kctl
;
113 memset(&info
, 0, sizeof(info
));
115 info
.id
.index
+= lctl
->index_offset
;
116 info
.id
.numid
+= lctl
->index_offset
;
117 result
= kctl
->info(kctl
, &info
);
120 memset(&value
, 0, sizeof(value
));
122 result
= kctl
->get(kctl
, &value
);
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
)
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
)
138 static void snd_ctl_led_set_state(struct snd_card
*card
, unsigned int access
,
139 struct snd_kcontrol
*kctl
, unsigned int ioff
)
141 struct snd_ctl_led
*led
;
142 struct snd_ctl_led_ctl
*lctl
;
146 led
= snd_ctl_led_get_by_access(access
);
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
])
155 list_for_each_entry(lctl
, &led
->controls
, list
) {
156 if (lctl
->kctl
== kctl
&& lctl
->index_offset
== ioff
)
158 UPDATE_ROUTE(route
, snd_ctl_led_get(lctl
));
160 if (!found
&& kctl
&& card
) {
161 lctl
= kzalloc(sizeof(*lctl
), GFP_KERNEL
);
164 lctl
->access
= access
;
166 lctl
->index_offset
= ioff
;
167 list_add(&lctl
->list
, &led
->controls
);
168 UPDATE_ROUTE(route
, snd_ctl_led_get(lctl
));
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;
179 struct led_trigger
*trig
= snd_ctl_ledtrig_audio
[led
->trigger_type
];
181 led_trigger_event(trig
, route
? LED_OFF
: LED_ON
);
185 static struct snd_ctl_led_ctl
*snd_ctl_led_find(struct snd_kcontrol
*kctl
, unsigned int ioff
)
187 struct list_head
*controls
;
188 struct snd_ctl_led_ctl
*lctl
;
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
)
200 static unsigned int snd_ctl_led_remove(struct snd_kcontrol
*kctl
, unsigned int ioff
,
203 struct snd_ctl_led_ctl
*lctl
;
204 unsigned int ret
= 0;
206 guard(mutex
)(&snd_ctl_led_mutex
);
207 lctl
= snd_ctl_led_find(kctl
, ioff
);
208 if (lctl
&& (access
== 0 || access
!= lctl
->access
)) {
210 list_del(&lctl
->list
);
216 static void snd_ctl_led_notify(struct snd_card
*card
, unsigned int mask
,
217 struct snd_kcontrol
*kctl
, unsigned int ioff
)
219 struct snd_kcontrol_volatile
*vd
;
220 unsigned int access
, access2
;
222 if (mask
== SNDRV_CTL_EVENT_MASK_REMOVE
) {
223 access
= snd_ctl_led_remove(kctl
, ioff
, 0);
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
);
231 snd_ctl_led_set_state(card
, access2
, NULL
, 0);
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
;
239 snd_ctl_led_set_state(card
, access
, kctl
, ioff
);
243 DEFINE_FREE(snd_card_unref
, struct snd_card
*, if (_T
) snd_card_unref(_T
))
245 static int snd_ctl_led_set_id(int card_number
, struct snd_ctl_elem_id
*id
,
246 unsigned int group
, bool set
)
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
;
253 card
= snd_card_ref(card_number
);
256 guard(rwsem_write
)(&card
->controls_rwsem
);
257 kctl
= snd_ctl_find_id(card
, id
);
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
))
265 new_access
= vd
->access
& ~SNDRV_CTL_ELEM_ACCESS_LED_MASK
;
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
);
275 static void snd_ctl_led_refresh(void)
279 for (group
= 0; group
< MAX_LED
; group
++)
280 snd_ctl_led_set_state(NULL
, group_to_access(group
), NULL
, 0);
283 static void snd_ctl_led_ctl_destroy(struct snd_ctl_led_ctl
*lctl
)
285 list_del(&lctl
->list
);
289 static void snd_ctl_led_clean(struct snd_card
*card
)
292 struct snd_ctl_led_ctl
*lctl
, *_lctl
;
293 struct snd_ctl_led
*led
;
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
);
303 static int snd_ctl_led_reset(int card_number
, unsigned int group
)
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
;
311 card
= snd_card_ref(card_number
);
315 scoped_guard(mutex
, &snd_ctl_led_mutex
) {
316 if (!snd_ctl_led_card_valid
[card_number
])
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
);
328 snd_ctl_led_set_state(NULL
, group_to_access(group
), NULL
, 0);
332 static void snd_ctl_led_register(struct snd_card
*card
)
334 struct snd_kcontrol
*kctl
;
337 if (snd_BUG_ON(card
->number
< 0 ||
338 card
->number
>= ARRAY_SIZE(snd_ctl_led_card_valid
)))
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
);
350 static void snd_ctl_led_disconnect(struct snd_card
*card
)
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
);
357 snd_ctl_led_refresh();
360 static void snd_ctl_led_card_release(struct device
*dev
)
362 struct snd_ctl_led_card
*led_card
= to_led_card_dev(dev
);
367 static void snd_ctl_led_release(struct device
*dev
)
371 static void snd_ctl_led_dev_release(struct device
*dev
)
379 static ssize_t
mode_show(struct device
*dev
,
380 struct device_attribute
*attr
, char *buf
)
382 struct snd_ctl_led
*led
= container_of(dev
, struct snd_ctl_led
, dev
);
383 const char *str
= NULL
;
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;
391 return sysfs_emit(buf
, "%s\n", str
);
394 static ssize_t
mode_store(struct device
*dev
,
395 struct device_attribute
*attr
,
396 const char *buf
, size_t count
)
398 struct snd_ctl_led
*led
= container_of(dev
, struct snd_ctl_led
, dev
);
400 size_t l
= min(count
, sizeof(_buf
) - 1);
401 enum snd_ctl_led_mode mode
;
403 memcpy(_buf
, buf
, l
);
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)
411 else if (strncmp(_buf
, "on", 2) == 0 || strncmp(_buf
, "1", 1) == 0)
416 scoped_guard(mutex
, &snd_ctl_led_mutex
)
419 snd_ctl_led_set_state(NULL
, group_to_access(led
->group
), NULL
, 0);
423 static ssize_t
brightness_show(struct device
*dev
,
424 struct device_attribute
*attr
, char *buf
)
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
];
429 return sysfs_emit(buf
, "%u\n", led_trigger_get_brightness(trig
));
432 static DEVICE_ATTR_RW(mode
);
433 static DEVICE_ATTR_RO(brightness
);
435 static struct attribute
*snd_ctl_led_dev_attrs
[] = {
437 &dev_attr_brightness
.attr
,
441 static const struct attribute_group snd_ctl_led_dev_attr_group
= {
442 .attrs
= snd_ctl_led_dev_attrs
,
445 static const struct attribute_group
*snd_ctl_led_dev_attr_groups
[] = {
446 &snd_ctl_led_dev_attr_group
,
450 static char *find_eos(char *s
)
452 while (*s
&& *s
!= ',')
459 static char *parse_uint(char *s
, unsigned int *val
)
461 unsigned long long res
;
462 if (kstrtoull(s
, 10, &res
))
468 static char *parse_string(char *s
, char *val
, size_t val_size
)
470 if (*s
== '"' || *s
== '\'') {
473 while (*s
&& *s
!= c
) {
481 while (*s
&& *s
!= ',') {
495 static char *parse_iface(char *s
, snd_ctl_elem_iface_t
*val
)
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
;
505 * These types of input strings are accepted:
507 * unsigned integer - numid (equivaled to numid=UINT)
508 * string - basic mixer name (equivalent to iface=MIXER,name=STR)
510 * [iface=MIXER,][device=UINT,][subdevice=UINT,]name=STR[,index=UINT]
512 static ssize_t
set_led_id(struct snd_ctl_led_card
*led_card
, const char *buf
, size_t count
,
515 char buf2
[256], *s
, *os
;
516 struct snd_ctl_elem_id id
;
519 if (strscpy(buf2
, buf
, sizeof(buf2
)) < 0)
521 memset(&id
, 0, sizeof(id
));
522 id
.iface
= SNDRV_CTL_ELEM_IFACE_MIXER
;
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
) {
540 if (*s
< '0' || *s
> '9')
545 parse_uint(buf2
, &id
.numid
);
547 for (; *s
>= ' '; s
++);
549 strscpy(id
.name
, buf2
, sizeof(id
.name
));
559 err
= snd_ctl_led_set_id(led_card
->number
, &id
, led_card
->led
->group
, attach
);
566 static ssize_t
attach_store(struct device
*dev
,
567 struct device_attribute
*attr
,
568 const char *buf
, size_t count
)
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);
574 static ssize_t
detach_store(struct device
*dev
,
575 struct device_attribute
*attr
,
576 const char *buf
, size_t count
)
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);
582 static ssize_t
reset_store(struct device
*dev
,
583 struct device_attribute
*attr
,
584 const char *buf
, size_t count
)
586 struct snd_ctl_led_card
*led_card
= container_of(dev
, struct snd_ctl_led_card
, dev
);
589 if (count
> 0 && buf
[0] == '1') {
590 err
= snd_ctl_led_reset(led_card
->number
, led_card
->led
->group
);
597 static ssize_t
list_show(struct device
*dev
,
598 struct device_attribute
*attr
, char *buf
)
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
;
605 card
= snd_card_ref(led_card
->number
);
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
)
615 l
+= sysfs_emit_at(buf
, l
, " ");
616 l
+= sysfs_emit_at(buf
, l
, "%u",
617 lctl
->kctl
->id
.numid
+ lctl
->index_offset
);
623 static DEVICE_ATTR_WO(attach
);
624 static DEVICE_ATTR_WO(detach
);
625 static DEVICE_ATTR_WO(reset
);
626 static DEVICE_ATTR_RO(list
);
628 static struct attribute
*snd_ctl_led_card_attrs
[] = {
629 &dev_attr_attach
.attr
,
630 &dev_attr_detach
.attr
,
631 &dev_attr_reset
.attr
,
636 static const struct attribute_group snd_ctl_led_card_attr_group
= {
637 .attrs
= snd_ctl_led_card_attrs
,
640 static const struct attribute_group
*snd_ctl_led_card_attr_groups
[] = {
641 &snd_ctl_led_card_attr_group
,
645 static struct device snd_ctl_led_dev
;
647 static void snd_ctl_led_sysfs_add(struct snd_card
*card
)
650 struct snd_ctl_led_card
*led_card
;
651 struct snd_ctl_led
*led
;
654 for (group
= 0; group
< MAX_LED
; group
++) {
655 led
= &snd_ctl_leds
[group
];
656 led_card
= kzalloc(sizeof(*led_card
), GFP_KERNEL
);
659 led_card
->number
= card
->number
;
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)
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
))
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
);
678 put_device(&led_card
->dev
);
680 dev_err(card
->dev
, "snd_ctl_led: unable to add card%d", card
->number
);
684 static void snd_ctl_led_sysfs_remove(struct snd_card
*card
)
687 struct snd_ctl_led_card
*led_card
;
688 struct snd_ctl_led
*led
;
691 for (group
= 0; group
< MAX_LED
; group
++) {
692 led
= &snd_ctl_leds
[group
];
693 led_card
= led
->cards
[card
->number
];
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
;
705 * Control layer registration
707 static 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
,
714 static int __init
snd_ctl_led_init(void)
716 struct snd_ctl_led
*led
;
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
]);
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
);
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
);
744 device_unregister(&snd_ctl_led_dev
);
748 snd_ctl_register_layer(&snd_ctl_led_lops
);
752 static void __exit
snd_ctl_led_exit(void)
754 struct snd_ctl_led
*led
;
755 struct snd_card
*card
;
756 unsigned int group
, card_number
;
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
])
762 card
= snd_card_ref(card_number
);
764 snd_ctl_led_sysfs_remove(card
);
765 snd_card_unref(card
);
768 for (group
= 0; group
< MAX_LED
; group
++) {
769 led
= &snd_ctl_leds
[group
];
770 device_unregister(&led
->dev
);
772 device_unregister(&snd_ctl_led_dev
);
773 snd_ctl_led_clean(NULL
);
775 led_trigger_unregister_simple(snd_ctl_ledtrig_audio
[LED_AUDIO_MUTE
]);
776 led_trigger_unregister_simple(snd_ctl_ledtrig_audio
[LED_AUDIO_MICMUTE
]);
779 module_init(snd_ctl_led_init
)
780 module_exit(snd_ctl_led_exit
)
782 MODULE_ALIAS("ledtrig:audio-mute");
783 MODULE_ALIAS("ledtrig:audio-micmute");