1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * LED state routines for driver control interface |
4 | * 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 | |
13 | MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>" ); |
14 | MODULE_DESCRIPTION("ALSA control interface to LED trigger code." ); |
15 | MODULE_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 | |
23 | enum snd_ctl_led_mode { |
24 | MODE_FOLLOW_MUTE = 0, |
25 | MODE_FOLLOW_ROUTE, |
26 | MODE_OFF, |
27 | MODE_ON, |
28 | }; |
29 | |
30 | struct snd_ctl_led_card { |
31 | struct device dev; |
32 | int number; |
33 | struct snd_ctl_led *led; |
34 | }; |
35 | |
36 | struct 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 | |
46 | struct 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 | |
54 | static DEFINE_MUTEX(snd_ctl_led_mutex); |
55 | static bool snd_ctl_led_card_valid[SNDRV_CARDS]; |
56 | static struct snd_ctl_led snd_ctl_leds[MAX_LED] = { |
57 | { |
58 | .name = "speaker" , |
59 | .group = (SNDRV_CTL_ELEM_ACCESS_SPK_LED >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1, |
60 | .trigger_type = LED_AUDIO_MUTE, |
61 | .mode = MODE_FOLLOW_MUTE, |
62 | }, |
63 | { |
64 | .name = "mic" , |
65 | .group = (SNDRV_CTL_ELEM_ACCESS_MIC_LED >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1, |
66 | .trigger_type = LED_AUDIO_MICMUTE, |
67 | .mode = MODE_FOLLOW_MUTE, |
68 | }, |
69 | }; |
70 | |
71 | static void snd_ctl_led_sysfs_add(struct snd_card *card); |
72 | static void snd_ctl_led_sysfs_remove(struct snd_card *card); |
73 | |
74 | #define UPDATE_ROUTE(route, cb) \ |
75 | do { \ |
76 | int route2 = (cb); \ |
77 | if (route2 >= 0) \ |
78 | route = route < 0 ? route2 : (route | route2); \ |
79 | } while (0) |
80 | |
81 | static inline unsigned int access_to_group(unsigned int access) |
82 | { |
83 | return ((access & SNDRV_CTL_ELEM_ACCESS_LED_MASK) >> |
84 | SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1; |
85 | } |
86 | |
87 | static inline unsigned int group_to_access(unsigned int group) |
88 | { |
89 | return (group + 1) << SNDRV_CTL_ELEM_ACCESS_LED_SHIFT; |
90 | } |
91 | |
92 | static struct snd_ctl_led *snd_ctl_led_get_by_access(unsigned int access) |
93 | { |
94 | unsigned int group = access_to_group(access); |
95 | if (group >= MAX_LED) |
96 | return NULL; |
97 | return &snd_ctl_leds[group]; |
98 | } |
99 | |
100 | /* |
101 | * A note for callers: |
102 | * The two static variables info and value are protected using snd_ctl_led_mutex. |
103 | */ |
104 | static int snd_ctl_led_get(struct snd_ctl_led_ctl *lctl) |
105 | { |
106 | static struct snd_ctl_elem_info info; |
107 | static struct snd_ctl_elem_value value; |
108 | struct snd_kcontrol *kctl = lctl->kctl; |
109 | unsigned int i; |
110 | int result; |
111 | |
112 | memset(&info, 0, sizeof(info)); |
113 | info.id = kctl->id; |
114 | info.id.index += lctl->index_offset; |
115 | info.id.numid += lctl->index_offset; |
116 | result = kctl->info(kctl, &info); |
117 | if (result < 0) |
118 | return -1; |
119 | memset(&value, 0, sizeof(value)); |
120 | value.id = info.id; |
121 | result = kctl->get(kctl, &value); |
122 | if (result < 0) |
123 | return -1; |
124 | if (info.type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || |
125 | info.type == SNDRV_CTL_ELEM_TYPE_INTEGER) { |
126 | for (i = 0; i < info.count; i++) |
127 | if (value.value.integer.value[i] != info.value.integer.min) |
128 | return 1; |
129 | } else if (info.type == SNDRV_CTL_ELEM_TYPE_INTEGER64) { |
130 | for (i = 0; i < info.count; i++) |
131 | if (value.value.integer64.value[i] != info.value.integer64.min) |
132 | return 1; |
133 | } |
134 | return 0; |
135 | } |
136 | |
137 | static void snd_ctl_led_set_state(struct snd_card *card, unsigned int access, |
138 | struct snd_kcontrol *kctl, unsigned int ioff) |
139 | { |
140 | struct snd_ctl_led *led; |
141 | struct snd_ctl_led_ctl *lctl; |
142 | int route; |
143 | bool found; |
144 | |
145 | led = snd_ctl_led_get_by_access(access); |
146 | if (!led) |
147 | return; |
148 | route = -1; |
149 | found = false; |
150 | scoped_guard(mutex, &snd_ctl_led_mutex) { |
151 | /* the card may not be registered (active) at this point */ |
152 | if (card && !snd_ctl_led_card_valid[card->number]) |
153 | return; |
154 | list_for_each_entry(lctl, &led->controls, list) { |
155 | if (lctl->kctl == kctl && lctl->index_offset == ioff) |
156 | found = true; |
157 | UPDATE_ROUTE(route, snd_ctl_led_get(lctl)); |
158 | } |
159 | if (!found && kctl && card) { |
160 | lctl = kzalloc(size: sizeof(*lctl), GFP_KERNEL); |
161 | if (lctl) { |
162 | lctl->card = card; |
163 | lctl->access = access; |
164 | lctl->kctl = kctl; |
165 | lctl->index_offset = ioff; |
166 | list_add(new: &lctl->list, head: &led->controls); |
167 | UPDATE_ROUTE(route, snd_ctl_led_get(lctl)); |
168 | } |
169 | } |
170 | } |
171 | switch (led->mode) { |
172 | case MODE_OFF: route = 1; break; |
173 | case MODE_ON: route = 0; break; |
174 | case MODE_FOLLOW_ROUTE: if (route >= 0) route ^= 1; break; |
175 | case MODE_FOLLOW_MUTE: /* noop */ break; |
176 | } |
177 | if (route >= 0) |
178 | ledtrig_audio_set(type: led->trigger_type, state: route ? LED_OFF : LED_ON); |
179 | } |
180 | |
181 | static struct snd_ctl_led_ctl *snd_ctl_led_find(struct snd_kcontrol *kctl, unsigned int ioff) |
182 | { |
183 | struct list_head *controls; |
184 | struct snd_ctl_led_ctl *lctl; |
185 | unsigned int group; |
186 | |
187 | for (group = 0; group < MAX_LED; group++) { |
188 | controls = &snd_ctl_leds[group].controls; |
189 | list_for_each_entry(lctl, controls, list) |
190 | if (lctl->kctl == kctl && lctl->index_offset == ioff) |
191 | return lctl; |
192 | } |
193 | return NULL; |
194 | } |
195 | |
196 | static unsigned int snd_ctl_led_remove(struct snd_kcontrol *kctl, unsigned int ioff, |
197 | unsigned int access) |
198 | { |
199 | struct snd_ctl_led_ctl *lctl; |
200 | unsigned int ret = 0; |
201 | |
202 | guard(mutex)(T: &snd_ctl_led_mutex); |
203 | lctl = snd_ctl_led_find(kctl, ioff); |
204 | if (lctl && (access == 0 || access != lctl->access)) { |
205 | ret = lctl->access; |
206 | list_del(entry: &lctl->list); |
207 | kfree(objp: lctl); |
208 | } |
209 | return ret; |
210 | } |
211 | |
212 | static void snd_ctl_led_notify(struct snd_card *card, unsigned int mask, |
213 | struct snd_kcontrol *kctl, unsigned int ioff) |
214 | { |
215 | struct snd_kcontrol_volatile *vd; |
216 | unsigned int access, access2; |
217 | |
218 | if (mask == SNDRV_CTL_EVENT_MASK_REMOVE) { |
219 | access = snd_ctl_led_remove(kctl, ioff, access: 0); |
220 | if (access) |
221 | snd_ctl_led_set_state(card, access, NULL, ioff: 0); |
222 | } else if (mask & SNDRV_CTL_EVENT_MASK_INFO) { |
223 | vd = &kctl->vd[ioff]; |
224 | access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; |
225 | access2 = snd_ctl_led_remove(kctl, ioff, access); |
226 | if (access2) |
227 | snd_ctl_led_set_state(card, access: access2, NULL, ioff: 0); |
228 | if (access) |
229 | snd_ctl_led_set_state(card, access, kctl, ioff); |
230 | } else if ((mask & (SNDRV_CTL_EVENT_MASK_ADD | |
231 | SNDRV_CTL_EVENT_MASK_VALUE)) != 0) { |
232 | vd = &kctl->vd[ioff]; |
233 | access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; |
234 | if (access) |
235 | snd_ctl_led_set_state(card, access, kctl, ioff); |
236 | } |
237 | } |
238 | |
239 | DEFINE_FREE(snd_card_unref, struct snd_card *, if (_T) snd_card_unref(_T)) |
240 | |
241 | static int snd_ctl_led_set_id(int card_number, struct snd_ctl_elem_id *id, |
242 | unsigned int group, bool set) |
243 | { |
244 | struct snd_card *card __free(snd_card_unref) = NULL; |
245 | struct snd_kcontrol *kctl; |
246 | struct snd_kcontrol_volatile *vd; |
247 | unsigned int ioff, access, new_access; |
248 | |
249 | card = snd_card_ref(card: card_number); |
250 | if (!card) |
251 | return -ENXIO; |
252 | guard(rwsem_write)(T: &card->controls_rwsem); |
253 | kctl = snd_ctl_find_id_locked(card, id); |
254 | if (!kctl) |
255 | return -ENOENT; |
256 | ioff = snd_ctl_get_ioff(kctl, id); |
257 | vd = &kctl->vd[ioff]; |
258 | access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; |
259 | if (access != 0 && access != group_to_access(group)) |
260 | return -EXDEV; |
261 | new_access = vd->access & ~SNDRV_CTL_ELEM_ACCESS_LED_MASK; |
262 | if (set) |
263 | new_access |= group_to_access(group); |
264 | if (new_access != vd->access) { |
265 | vd->access = new_access; |
266 | snd_ctl_led_notify(card, SNDRV_CTL_EVENT_MASK_INFO, kctl, ioff); |
267 | } |
268 | return 0; |
269 | } |
270 | |
271 | static void snd_ctl_led_refresh(void) |
272 | { |
273 | unsigned int group; |
274 | |
275 | for (group = 0; group < MAX_LED; group++) |
276 | snd_ctl_led_set_state(NULL, access: group_to_access(group), NULL, ioff: 0); |
277 | } |
278 | |
279 | static void snd_ctl_led_ctl_destroy(struct snd_ctl_led_ctl *lctl) |
280 | { |
281 | list_del(entry: &lctl->list); |
282 | kfree(objp: lctl); |
283 | } |
284 | |
285 | static void snd_ctl_led_clean(struct snd_card *card) |
286 | { |
287 | unsigned int group; |
288 | struct snd_ctl_led *led; |
289 | struct snd_ctl_led_ctl *lctl; |
290 | |
291 | for (group = 0; group < MAX_LED; group++) { |
292 | led = &snd_ctl_leds[group]; |
293 | repeat: |
294 | list_for_each_entry(lctl, &led->controls, list) |
295 | if (!card || lctl->card == card) { |
296 | snd_ctl_led_ctl_destroy(lctl); |
297 | goto repeat; |
298 | } |
299 | } |
300 | } |
301 | |
302 | static int snd_ctl_led_reset(int card_number, unsigned int group) |
303 | { |
304 | struct snd_card *card __free(snd_card_unref) = NULL; |
305 | struct snd_ctl_led *led; |
306 | struct snd_ctl_led_ctl *lctl; |
307 | struct snd_kcontrol_volatile *vd; |
308 | bool change = false; |
309 | |
310 | card = snd_card_ref(card: card_number); |
311 | if (!card) |
312 | return -ENXIO; |
313 | |
314 | scoped_guard(mutex, &snd_ctl_led_mutex) { |
315 | if (!snd_ctl_led_card_valid[card_number]) |
316 | return -ENXIO; |
317 | led = &snd_ctl_leds[group]; |
318 | repeat: |
319 | list_for_each_entry(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 | goto repeat; |
326 | } |
327 | } |
328 | if (change) |
329 | snd_ctl_led_set_state(NULL, access: group_to_access(group), NULL, ioff: 0); |
330 | return 0; |
331 | } |
332 | |
333 | static void snd_ctl_led_register(struct snd_card *card) |
334 | { |
335 | struct snd_kcontrol *kctl; |
336 | unsigned int ioff; |
337 | |
338 | if (snd_BUG_ON(card->number < 0 || |
339 | card->number >= ARRAY_SIZE(snd_ctl_led_card_valid))) |
340 | return; |
341 | scoped_guard(mutex, &snd_ctl_led_mutex) |
342 | snd_ctl_led_card_valid[card->number] = true; |
343 | /* the register callback is already called with held card->controls_rwsem */ |
344 | list_for_each_entry(kctl, &card->controls, list) |
345 | for (ioff = 0; ioff < kctl->count; ioff++) |
346 | snd_ctl_led_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, kctl, ioff); |
347 | snd_ctl_led_refresh(); |
348 | snd_ctl_led_sysfs_add(card); |
349 | } |
350 | |
351 | static void snd_ctl_led_disconnect(struct snd_card *card) |
352 | { |
353 | snd_ctl_led_sysfs_remove(card); |
354 | scoped_guard(mutex, &snd_ctl_led_mutex) { |
355 | snd_ctl_led_card_valid[card->number] = false; |
356 | snd_ctl_led_clean(card); |
357 | } |
358 | snd_ctl_led_refresh(); |
359 | } |
360 | |
361 | static void snd_ctl_led_card_release(struct device *dev) |
362 | { |
363 | struct snd_ctl_led_card *led_card = to_led_card_dev(dev); |
364 | |
365 | kfree(objp: led_card); |
366 | } |
367 | |
368 | static void snd_ctl_led_release(struct device *dev) |
369 | { |
370 | } |
371 | |
372 | static void snd_ctl_led_dev_release(struct device *dev) |
373 | { |
374 | } |
375 | |
376 | /* |
377 | * sysfs |
378 | */ |
379 | |
380 | static ssize_t mode_show(struct device *dev, |
381 | struct device_attribute *attr, char *buf) |
382 | { |
383 | struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); |
384 | const char *str = NULL; |
385 | |
386 | switch (led->mode) { |
387 | case MODE_FOLLOW_MUTE: str = "follow-mute" ; break; |
388 | case MODE_FOLLOW_ROUTE: str = "follow-route" ; break; |
389 | case MODE_ON: str = "on" ; break; |
390 | case MODE_OFF: str = "off" ; break; |
391 | } |
392 | return sysfs_emit(buf, fmt: "%s\n" , str); |
393 | } |
394 | |
395 | static ssize_t mode_store(struct device *dev, |
396 | struct device_attribute *attr, |
397 | const char *buf, size_t count) |
398 | { |
399 | struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); |
400 | char _buf[16]; |
401 | size_t l = min(count, sizeof(_buf) - 1); |
402 | enum snd_ctl_led_mode mode; |
403 | |
404 | memcpy(_buf, buf, l); |
405 | _buf[l] = '\0'; |
406 | if (strstr(_buf, "mute" )) |
407 | mode = MODE_FOLLOW_MUTE; |
408 | else if (strstr(_buf, "route" )) |
409 | mode = MODE_FOLLOW_ROUTE; |
410 | else if (strncmp(_buf, "off" , 3) == 0 || strncmp(_buf, "0" , 1) == 0) |
411 | mode = MODE_OFF; |
412 | else if (strncmp(_buf, "on" , 2) == 0 || strncmp(_buf, "1" , 1) == 0) |
413 | mode = MODE_ON; |
414 | else |
415 | return count; |
416 | |
417 | scoped_guard(mutex, &snd_ctl_led_mutex) |
418 | led->mode = mode; |
419 | |
420 | snd_ctl_led_set_state(NULL, access: group_to_access(group: led->group), NULL, ioff: 0); |
421 | return count; |
422 | } |
423 | |
424 | static ssize_t brightness_show(struct device *dev, |
425 | struct device_attribute *attr, char *buf) |
426 | { |
427 | struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); |
428 | |
429 | return sysfs_emit(buf, fmt: "%u\n" , ledtrig_audio_get(type: led->trigger_type)); |
430 | } |
431 | |
432 | static DEVICE_ATTR_RW(mode); |
433 | static DEVICE_ATTR_RO(brightness); |
434 | |
435 | static struct attribute *snd_ctl_led_dev_attrs[] = { |
436 | &dev_attr_mode.attr, |
437 | &dev_attr_brightness.attr, |
438 | NULL, |
439 | }; |
440 | |
441 | static const struct attribute_group snd_ctl_led_dev_attr_group = { |
442 | .attrs = snd_ctl_led_dev_attrs, |
443 | }; |
444 | |
445 | static const struct attribute_group *snd_ctl_led_dev_attr_groups[] = { |
446 | &snd_ctl_led_dev_attr_group, |
447 | NULL, |
448 | }; |
449 | |
450 | static char *find_eos(char *s) |
451 | { |
452 | while (*s && *s != ',') |
453 | s++; |
454 | if (*s) |
455 | s++; |
456 | return s; |
457 | } |
458 | |
459 | static char *parse_uint(char *s, unsigned int *val) |
460 | { |
461 | unsigned long long res; |
462 | if (kstrtoull(s, base: 10, res: &res)) |
463 | res = 0; |
464 | *val = res; |
465 | return find_eos(s); |
466 | } |
467 | |
468 | static 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 | |
495 | static char *parse_iface(char *s, snd_ctl_elem_iface_t *val) |
496 | { |
497 | if (!strncasecmp(s1: s, s2: "card" , n: 4)) |
498 | *val = SNDRV_CTL_ELEM_IFACE_CARD; |
499 | else if (!strncasecmp(s1: s, s2: "mixer" , n: 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=UINT |
510 | * [iface=MIXER,][device=UINT,][subdevice=UINT,]name=STR[,index=UINT] |
511 | */ |
512 | static 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(s1: s, s2: "numid=" , n: 6)) { |
527 | s = parse_uint(s: s + 6, val: &id.numid); |
528 | } else if (!strncasecmp(s1: s, s2: "iface=" , n: 6)) { |
529 | s = parse_iface(s: s + 6, val: &id.iface); |
530 | } else if (!strncasecmp(s1: s, s2: "device=" , n: 7)) { |
531 | s = parse_uint(s: s + 7, val: &id.device); |
532 | } else if (!strncasecmp(s1: s, s2: "subdevice=" , n: 10)) { |
533 | s = parse_uint(s: s + 10, val: &id.subdevice); |
534 | } else if (!strncasecmp(s1: s, s2: "name=" , n: 5)) { |
535 | s = parse_string(s: s + 5, val: id.name, val_size: sizeof(id.name)); |
536 | } else if (!strncasecmp(s1: s, s2: "index=" , n: 6)) { |
537 | s = parse_uint(s: s + 6, val: &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(s: buf2, val: &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(card_number: led_card->number, id: &id, group: led_card->led->group, set: attach); |
560 | if (err < 0) |
561 | return err; |
562 | |
563 | return count; |
564 | } |
565 | |
566 | static 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, attach: true); |
572 | } |
573 | |
574 | static 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, attach: false); |
580 | } |
581 | |
582 | static 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(card_number: led_card->number, group: led_card->led->group); |
591 | if (err < 0) |
592 | return err; |
593 | } |
594 | return count; |
595 | } |
596 | |
597 | static 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(card: led_card->number); |
606 | if (!card) |
607 | return -ENXIO; |
608 | guard(rwsem_read)(T: &card->controls_rwsem); |
609 | guard(mutex)(T: &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, at: l, fmt: " " ); |
616 | l += sysfs_emit_at(buf, at: l, fmt: "%u" , |
617 | lctl->kctl->id.numid + lctl->index_offset); |
618 | } |
619 | } |
620 | return l; |
621 | } |
622 | |
623 | static DEVICE_ATTR_WO(attach); |
624 | static DEVICE_ATTR_WO(detach); |
625 | static DEVICE_ATTR_WO(reset); |
626 | static DEVICE_ATTR_RO(list); |
627 | |
628 | static 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 | |
636 | static const struct attribute_group snd_ctl_led_card_attr_group = { |
637 | .attrs = snd_ctl_led_card_attrs, |
638 | }; |
639 | |
640 | static const struct attribute_group *snd_ctl_led_card_attr_groups[] = { |
641 | &snd_ctl_led_card_attr_group, |
642 | NULL, |
643 | }; |
644 | |
645 | static struct device snd_ctl_led_dev; |
646 | |
647 | static 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(size: 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(dev: &led_card->dev); |
662 | led_card->dev.release = snd_ctl_led_card_release; |
663 | if (dev_set_name(dev: &led_card->dev, name: "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(dev: &led_card->dev)) |
668 | goto cerr; |
669 | led->cards[card->number] = led_card; |
670 | snprintf(buf: link_name, size: sizeof(link_name), fmt: "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; |
677 | cerr: |
678 | put_device(dev: &led_card->dev); |
679 | cerr2: |
680 | printk(KERN_ERR "snd_ctl_led: unable to add card%d" , card->number); |
681 | } |
682 | } |
683 | |
684 | static 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(buf: link_name, size: sizeof(link_name), fmt: "led-%s" , led->name); |
697 | sysfs_remove_link(kobj: &card->ctl_dev->kobj, name: link_name); |
698 | sysfs_remove_link(kobj: &led_card->dev.kobj, name: "card" ); |
699 | device_unregister(dev: &led_card->dev); |
700 | led->cards[card->number] = NULL; |
701 | } |
702 | } |
703 | |
704 | /* |
705 | * Control layer registration |
706 | */ |
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, |
712 | }; |
713 | |
714 | static int __init snd_ctl_led_init(void) |
715 | { |
716 | struct snd_ctl_led *led; |
717 | unsigned int group; |
718 | |
719 | device_initialize(dev: &snd_ctl_led_dev); |
720 | snd_ctl_led_dev.class = &sound_class; |
721 | snd_ctl_led_dev.release = snd_ctl_led_dev_release; |
722 | dev_set_name(dev: &snd_ctl_led_dev, name: "ctl-led" ); |
723 | if (device_add(dev: &snd_ctl_led_dev)) { |
724 | put_device(dev: &snd_ctl_led_dev); |
725 | return -ENOMEM; |
726 | } |
727 | for (group = 0; group < MAX_LED; group++) { |
728 | led = &snd_ctl_leds[group]; |
729 | INIT_LIST_HEAD(list: &led->controls); |
730 | device_initialize(dev: &led->dev); |
731 | led->dev.parent = &snd_ctl_led_dev; |
732 | led->dev.release = snd_ctl_led_release; |
733 | led->dev.groups = snd_ctl_led_dev_attr_groups; |
734 | dev_set_name(dev: &led->dev, name: led->name); |
735 | if (device_add(dev: &led->dev)) { |
736 | put_device(dev: &led->dev); |
737 | for (; group > 0; group--) { |
738 | led = &snd_ctl_leds[group - 1]; |
739 | device_unregister(dev: &led->dev); |
740 | } |
741 | device_unregister(dev: &snd_ctl_led_dev); |
742 | return -ENOMEM; |
743 | } |
744 | } |
745 | snd_ctl_register_layer(lops: &snd_ctl_led_lops); |
746 | return 0; |
747 | } |
748 | |
749 | static void __exit snd_ctl_led_exit(void) |
750 | { |
751 | struct snd_ctl_led *led; |
752 | struct snd_card *card; |
753 | unsigned int group, card_number; |
754 | |
755 | snd_ctl_disconnect_layer(lops: &snd_ctl_led_lops); |
756 | for (card_number = 0; card_number < SNDRV_CARDS; card_number++) { |
757 | if (!snd_ctl_led_card_valid[card_number]) |
758 | continue; |
759 | card = snd_card_ref(card: card_number); |
760 | if (card) { |
761 | snd_ctl_led_sysfs_remove(card); |
762 | snd_card_unref(card); |
763 | } |
764 | } |
765 | for (group = 0; group < MAX_LED; group++) { |
766 | led = &snd_ctl_leds[group]; |
767 | device_unregister(dev: &led->dev); |
768 | } |
769 | device_unregister(dev: &snd_ctl_led_dev); |
770 | snd_ctl_led_clean(NULL); |
771 | } |
772 | |
773 | module_init(snd_ctl_led_init) |
774 | module_exit(snd_ctl_led_exit) |
775 | |