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