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 | ||
cb17fe00 | 97 | static int snd_ctl_led_get(struct snd_ctl_led_ctl *lctl) |
22d8de62 JK |
98 | { |
99 | struct snd_kcontrol *kctl = lctl->kctl; | |
100 | struct snd_ctl_elem_info info; | |
101 | struct snd_ctl_elem_value value; | |
102 | unsigned int i; | |
103 | int result; | |
104 | ||
105 | memset(&info, 0, sizeof(info)); | |
106 | info.id = kctl->id; | |
107 | info.id.index += lctl->index_offset; | |
108 | info.id.numid += lctl->index_offset; | |
109 | result = kctl->info(kctl, &info); | |
110 | if (result < 0) | |
111 | return -1; | |
112 | memset(&value, 0, sizeof(value)); | |
113 | value.id = info.id; | |
114 | result = kctl->get(kctl, &value); | |
115 | if (result < 0) | |
116 | return -1; | |
117 | if (info.type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || | |
118 | info.type == SNDRV_CTL_ELEM_TYPE_INTEGER) { | |
119 | for (i = 0; i < info.count; i++) | |
120 | if (value.value.integer.value[i] != info.value.integer.min) | |
121 | return 1; | |
122 | } else if (info.type == SNDRV_CTL_ELEM_TYPE_INTEGER64) { | |
123 | for (i = 0; i < info.count; i++) | |
124 | if (value.value.integer64.value[i] != info.value.integer64.min) | |
125 | return 1; | |
126 | } | |
127 | return 0; | |
128 | } | |
129 | ||
130 | static void snd_ctl_led_set_state(struct snd_card *card, unsigned int access, | |
131 | struct snd_kcontrol *kctl, unsigned int ioff) | |
132 | { | |
cb17fe00 JK |
133 | struct snd_ctl_led *led; |
134 | struct snd_ctl_led_ctl *lctl; | |
22d8de62 JK |
135 | int route; |
136 | bool found; | |
137 | ||
cb17fe00 JK |
138 | led = snd_ctl_led_get_by_access(access); |
139 | if (!led) | |
22d8de62 | 140 | return; |
22d8de62 JK |
141 | route = -1; |
142 | found = false; | |
143 | mutex_lock(&snd_ctl_led_mutex); | |
144 | /* the card may not be registered (active) at this point */ | |
145 | if (card && !snd_ctl_led_card_valid[card->number]) { | |
146 | mutex_unlock(&snd_ctl_led_mutex); | |
147 | return; | |
148 | } | |
cb17fe00 | 149 | list_for_each_entry(lctl, &led->controls, list) { |
22d8de62 JK |
150 | if (lctl->kctl == kctl && lctl->index_offset == ioff) |
151 | found = true; | |
152 | UPDATE_ROUTE(route, snd_ctl_led_get(lctl)); | |
153 | } | |
154 | if (!found && kctl && card) { | |
155 | lctl = kzalloc(sizeof(*lctl), GFP_KERNEL); | |
156 | if (lctl) { | |
157 | lctl->card = card; | |
158 | lctl->access = access; | |
159 | lctl->kctl = kctl; | |
160 | lctl->index_offset = ioff; | |
cb17fe00 | 161 | list_add(&lctl->list, &led->controls); |
22d8de62 JK |
162 | UPDATE_ROUTE(route, snd_ctl_led_get(lctl)); |
163 | } | |
164 | } | |
165 | mutex_unlock(&snd_ctl_led_mutex); | |
cb17fe00 JK |
166 | switch (led->mode) { |
167 | case MODE_OFF: route = 1; break; | |
168 | case MODE_ON: route = 0; break; | |
169 | case MODE_FOLLOW_ROUTE: if (route >= 0) route ^= 1; break; | |
170 | case MODE_FOLLOW_MUTE: /* noop */ break; | |
171 | } | |
22d8de62 | 172 | if (route >= 0) |
cb17fe00 | 173 | ledtrig_audio_set(led->trigger_type, route ? LED_OFF : LED_ON); |
22d8de62 JK |
174 | } |
175 | ||
cb17fe00 | 176 | static struct snd_ctl_led_ctl *snd_ctl_led_find(struct snd_kcontrol *kctl, unsigned int ioff) |
22d8de62 JK |
177 | { |
178 | struct list_head *controls; | |
cb17fe00 | 179 | struct snd_ctl_led_ctl *lctl; |
22d8de62 JK |
180 | unsigned int group; |
181 | ||
182 | for (group = 0; group < MAX_LED; group++) { | |
cb17fe00 | 183 | controls = &snd_ctl_leds[group].controls; |
22d8de62 JK |
184 | list_for_each_entry(lctl, controls, list) |
185 | if (lctl->kctl == kctl && lctl->index_offset == ioff) | |
186 | return lctl; | |
187 | } | |
188 | return NULL; | |
189 | } | |
190 | ||
191 | static unsigned int snd_ctl_led_remove(struct snd_kcontrol *kctl, unsigned int ioff, | |
192 | unsigned int access) | |
193 | { | |
cb17fe00 | 194 | struct snd_ctl_led_ctl *lctl; |
22d8de62 JK |
195 | unsigned int ret = 0; |
196 | ||
197 | mutex_lock(&snd_ctl_led_mutex); | |
198 | lctl = snd_ctl_led_find(kctl, ioff); | |
199 | if (lctl && (access == 0 || access != lctl->access)) { | |
200 | ret = lctl->access; | |
201 | list_del(&lctl->list); | |
202 | kfree(lctl); | |
203 | } | |
204 | mutex_unlock(&snd_ctl_led_mutex); | |
205 | return ret; | |
206 | } | |
207 | ||
208 | static void snd_ctl_led_notify(struct snd_card *card, unsigned int mask, | |
209 | struct snd_kcontrol *kctl, unsigned int ioff) | |
210 | { | |
211 | struct snd_kcontrol_volatile *vd; | |
212 | unsigned int access, access2; | |
213 | ||
214 | if (mask == SNDRV_CTL_EVENT_MASK_REMOVE) { | |
215 | access = snd_ctl_led_remove(kctl, ioff, 0); | |
216 | if (access) | |
217 | snd_ctl_led_set_state(card, access, NULL, 0); | |
218 | } else if (mask & SNDRV_CTL_EVENT_MASK_INFO) { | |
219 | vd = &kctl->vd[ioff]; | |
220 | access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; | |
221 | access2 = snd_ctl_led_remove(kctl, ioff, access); | |
222 | if (access2) | |
223 | snd_ctl_led_set_state(card, access2, NULL, 0); | |
224 | if (access) | |
225 | snd_ctl_led_set_state(card, access, kctl, ioff); | |
226 | } else if ((mask & (SNDRV_CTL_EVENT_MASK_ADD | | |
227 | SNDRV_CTL_EVENT_MASK_VALUE)) != 0) { | |
228 | vd = &kctl->vd[ioff]; | |
229 | access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; | |
230 | if (access) | |
231 | snd_ctl_led_set_state(card, access, kctl, ioff); | |
232 | } | |
233 | } | |
234 | ||
a135dfb5 JK |
235 | static int snd_ctl_led_set_id(int card_number, struct snd_ctl_elem_id *id, |
236 | unsigned int group, bool set) | |
237 | { | |
238 | struct snd_card *card; | |
239 | struct snd_kcontrol *kctl; | |
240 | struct snd_kcontrol_volatile *vd; | |
241 | unsigned int ioff, access, new_access; | |
242 | int err = 0; | |
243 | ||
244 | card = snd_card_ref(card_number); | |
245 | if (card) { | |
246 | down_write(&card->controls_rwsem); | |
247 | kctl = snd_ctl_find_id(card, id); | |
248 | if (kctl) { | |
249 | ioff = snd_ctl_get_ioff(kctl, id); | |
250 | vd = &kctl->vd[ioff]; | |
251 | access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; | |
252 | if (access != 0 && access != group_to_access(group)) { | |
253 | err = -EXDEV; | |
254 | goto unlock; | |
255 | } | |
256 | new_access = vd->access & ~SNDRV_CTL_ELEM_ACCESS_LED_MASK; | |
257 | if (set) | |
258 | new_access |= group_to_access(group); | |
259 | if (new_access != vd->access) { | |
260 | vd->access = new_access; | |
261 | snd_ctl_led_notify(card, SNDRV_CTL_EVENT_MASK_INFO, kctl, ioff); | |
262 | } | |
263 | } else { | |
264 | err = -ENOENT; | |
265 | } | |
266 | unlock: | |
267 | up_write(&card->controls_rwsem); | |
268 | snd_card_unref(card); | |
269 | } else { | |
270 | err = -ENXIO; | |
271 | } | |
272 | return err; | |
273 | } | |
274 | ||
22d8de62 JK |
275 | static 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 | ||
a135dfb5 JK |
283 | static void snd_ctl_led_ctl_destroy(struct snd_ctl_led_ctl *lctl) |
284 | { | |
285 | list_del(&lctl->list); | |
286 | kfree(lctl); | |
287 | } | |
288 | ||
22d8de62 JK |
289 | static void snd_ctl_led_clean(struct snd_card *card) |
290 | { | |
291 | unsigned int group; | |
cb17fe00 JK |
292 | struct snd_ctl_led *led; |
293 | struct snd_ctl_led_ctl *lctl; | |
22d8de62 JK |
294 | |
295 | for (group = 0; group < MAX_LED; group++) { | |
cb17fe00 | 296 | led = &snd_ctl_leds[group]; |
22d8de62 | 297 | repeat: |
cb17fe00 | 298 | list_for_each_entry(lctl, &led->controls, list) |
22d8de62 | 299 | if (!card || lctl->card == card) { |
a135dfb5 | 300 | snd_ctl_led_ctl_destroy(lctl); |
22d8de62 JK |
301 | goto repeat; |
302 | } | |
303 | } | |
304 | } | |
305 | ||
a135dfb5 JK |
306 | static int snd_ctl_led_reset(int card_number, unsigned int group) |
307 | { | |
308 | struct snd_card *card; | |
309 | struct snd_ctl_led *led; | |
310 | struct snd_ctl_led_ctl *lctl; | |
311 | struct snd_kcontrol_volatile *vd; | |
312 | bool change = false; | |
313 | ||
314 | card = snd_card_ref(card_number); | |
315 | if (!card) | |
316 | return -ENXIO; | |
317 | ||
318 | mutex_lock(&snd_ctl_led_mutex); | |
319 | if (!snd_ctl_led_card_valid[card_number]) { | |
320 | mutex_unlock(&snd_ctl_led_mutex); | |
321 | snd_card_unref(card); | |
322 | return -ENXIO; | |
323 | } | |
324 | led = &snd_ctl_leds[group]; | |
325 | repeat: | |
326 | list_for_each_entry(lctl, &led->controls, list) | |
327 | if (lctl->card == card) { | |
328 | vd = &lctl->kctl->vd[lctl->index_offset]; | |
329 | vd->access &= ~group_to_access(group); | |
330 | snd_ctl_led_ctl_destroy(lctl); | |
331 | change = true; | |
332 | goto repeat; | |
333 | } | |
334 | mutex_unlock(&snd_ctl_led_mutex); | |
335 | if (change) | |
336 | snd_ctl_led_set_state(NULL, group_to_access(group), NULL, 0); | |
337 | snd_card_unref(card); | |
338 | return 0; | |
339 | } | |
340 | ||
22d8de62 JK |
341 | static void snd_ctl_led_register(struct snd_card *card) |
342 | { | |
343 | struct snd_kcontrol *kctl; | |
344 | unsigned int ioff; | |
345 | ||
346 | if (snd_BUG_ON(card->number < 0 || | |
347 | card->number >= ARRAY_SIZE(snd_ctl_led_card_valid))) | |
348 | return; | |
349 | mutex_lock(&snd_ctl_led_mutex); | |
350 | snd_ctl_led_card_valid[card->number] = true; | |
351 | mutex_unlock(&snd_ctl_led_mutex); | |
352 | /* the register callback is already called with held card->controls_rwsem */ | |
353 | list_for_each_entry(kctl, &card->controls, list) | |
354 | for (ioff = 0; ioff < kctl->count; ioff++) | |
355 | snd_ctl_led_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, kctl, ioff); | |
356 | snd_ctl_led_refresh(); | |
a135dfb5 | 357 | snd_ctl_led_sysfs_add(card); |
22d8de62 JK |
358 | } |
359 | ||
360 | static void snd_ctl_led_disconnect(struct snd_card *card) | |
361 | { | |
a135dfb5 | 362 | snd_ctl_led_sysfs_remove(card); |
22d8de62 JK |
363 | mutex_lock(&snd_ctl_led_mutex); |
364 | snd_ctl_led_card_valid[card->number] = false; | |
365 | snd_ctl_led_clean(card); | |
366 | mutex_unlock(&snd_ctl_led_mutex); | |
367 | snd_ctl_led_refresh(); | |
368 | } | |
369 | ||
cb17fe00 JK |
370 | /* |
371 | * sysfs | |
372 | */ | |
373 | ||
374 | static ssize_t show_mode(struct device *dev, | |
375 | struct device_attribute *attr, char *buf) | |
376 | { | |
377 | struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); | |
378 | const char *str; | |
379 | ||
380 | switch (led->mode) { | |
381 | case MODE_FOLLOW_MUTE: str = "follow-mute"; break; | |
382 | case MODE_FOLLOW_ROUTE: str = "follow-route"; break; | |
383 | case MODE_ON: str = "on"; break; | |
384 | case MODE_OFF: str = "off"; break; | |
385 | } | |
386 | return sprintf(buf, "%s\n", str); | |
387 | } | |
388 | ||
389 | static ssize_t store_mode(struct device *dev, struct device_attribute *attr, | |
390 | const char *buf, size_t count) | |
391 | { | |
392 | struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); | |
393 | char _buf[16]; | |
394 | size_t l = min(count, sizeof(_buf) - 1) + 1; | |
395 | enum snd_ctl_led_mode mode; | |
396 | ||
397 | memcpy(_buf, buf, l); | |
398 | _buf[l] = '\0'; | |
399 | if (strstr(_buf, "mute")) | |
400 | mode = MODE_FOLLOW_MUTE; | |
401 | else if (strstr(_buf, "route")) | |
402 | mode = MODE_FOLLOW_ROUTE; | |
403 | else if (strncmp(_buf, "off", 3) == 0 || strncmp(_buf, "0", 1) == 0) | |
404 | mode = MODE_OFF; | |
405 | else if (strncmp(_buf, "on", 2) == 0 || strncmp(_buf, "1", 1) == 0) | |
406 | mode = MODE_ON; | |
407 | else | |
408 | return count; | |
409 | ||
410 | mutex_lock(&snd_ctl_led_mutex); | |
411 | led->mode = mode; | |
412 | mutex_unlock(&snd_ctl_led_mutex); | |
413 | ||
414 | snd_ctl_led_set_state(NULL, group_to_access(led->group), NULL, 0); | |
415 | return count; | |
416 | } | |
417 | ||
418 | static ssize_t show_brightness(struct device *dev, | |
419 | struct device_attribute *attr, char *buf) | |
420 | { | |
421 | struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); | |
422 | ||
423 | return sprintf(buf, "%u\n", ledtrig_audio_get(led->trigger_type)); | |
424 | } | |
425 | ||
426 | static DEVICE_ATTR(mode, 0644, show_mode, store_mode); | |
427 | static DEVICE_ATTR(brightness, 0444, show_brightness, NULL); | |
428 | ||
429 | static struct attribute *snd_ctl_led_dev_attrs[] = { | |
430 | &dev_attr_mode.attr, | |
431 | &dev_attr_brightness.attr, | |
432 | NULL, | |
433 | }; | |
434 | ||
435 | static const struct attribute_group snd_ctl_led_dev_attr_group = { | |
436 | .attrs = snd_ctl_led_dev_attrs, | |
437 | }; | |
438 | ||
439 | static const struct attribute_group *snd_ctl_led_dev_attr_groups[] = { | |
440 | &snd_ctl_led_dev_attr_group, | |
441 | NULL, | |
442 | }; | |
443 | ||
a135dfb5 JK |
444 | static char *find_eos(char *s) |
445 | { | |
446 | while (*s && *s != ',') | |
447 | s++; | |
448 | if (*s) | |
449 | s++; | |
450 | return s; | |
451 | } | |
452 | ||
453 | static char *parse_uint(char *s, unsigned int *val) | |
454 | { | |
455 | unsigned long long res; | |
456 | if (kstrtoull(s, 10, &res)) | |
457 | res = 0; | |
458 | *val = res; | |
459 | return find_eos(s); | |
460 | } | |
461 | ||
462 | static char *parse_string(char *s, char *val, size_t val_size) | |
463 | { | |
464 | if (*s == '"' || *s == '\'') { | |
465 | char c = *s; | |
466 | s++; | |
467 | while (*s && *s != c) { | |
468 | if (val_size > 1) { | |
469 | *val++ = *s; | |
470 | val_size--; | |
471 | } | |
472 | s++; | |
473 | } | |
474 | } else { | |
475 | while (*s && *s != ',') { | |
476 | if (val_size > 1) { | |
477 | *val++ = *s; | |
478 | val_size--; | |
479 | } | |
480 | s++; | |
481 | } | |
482 | } | |
483 | *val = '\0'; | |
484 | if (*s) | |
485 | s++; | |
486 | return s; | |
487 | } | |
488 | ||
489 | static char *parse_iface(char *s, unsigned int *val) | |
490 | { | |
491 | if (!strncasecmp(s, "card", 4)) | |
492 | *val = SNDRV_CTL_ELEM_IFACE_CARD; | |
493 | else if (!strncasecmp(s, "mixer", 5)) | |
494 | *val = SNDRV_CTL_ELEM_IFACE_MIXER; | |
495 | return find_eos(s); | |
496 | } | |
497 | ||
498 | /* | |
499 | * These types of input strings are accepted: | |
500 | * | |
501 | * unsigned integer - numid (equivaled to numid=UINT) | |
502 | * string - basic mixer name (equivalent to iface=MIXER,name=STR) | |
503 | * numid=UINT | |
504 | * [iface=MIXER,][device=UINT,][subdevice=UINT,]name=STR[,index=UINT] | |
505 | */ | |
506 | static ssize_t set_led_id(struct snd_ctl_led_card *led_card, const char *buf, size_t count, | |
507 | bool attach) | |
508 | { | |
62327ebb | 509 | char buf2[256], *s, *os; |
a135dfb5 JK |
510 | size_t len = max(sizeof(s) - 1, count); |
511 | struct snd_ctl_elem_id id; | |
512 | int err; | |
513 | ||
514 | strncpy(buf2, buf, len); | |
515 | buf2[len] = '\0'; | |
516 | memset(&id, 0, sizeof(id)); | |
517 | id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; | |
518 | s = buf2; | |
519 | while (*s) { | |
62327ebb | 520 | os = s; |
a135dfb5 JK |
521 | if (!strncasecmp(s, "numid=", 6)) { |
522 | s = parse_uint(s + 6, &id.numid); | |
523 | } else if (!strncasecmp(s, "iface=", 6)) { | |
524 | s = parse_iface(s + 6, &id.iface); | |
525 | } else if (!strncasecmp(s, "device=", 7)) { | |
526 | s = parse_uint(s + 7, &id.device); | |
527 | } else if (!strncasecmp(s, "subdevice=", 10)) { | |
528 | s = parse_uint(s + 10, &id.subdevice); | |
529 | } else if (!strncasecmp(s, "name=", 5)) { | |
530 | s = parse_string(s + 5, id.name, sizeof(id.name)); | |
531 | } else if (!strncasecmp(s, "index=", 6)) { | |
532 | s = parse_uint(s + 6, &id.index); | |
533 | } else if (s == buf2) { | |
534 | while (*s) { | |
535 | if (*s < '0' || *s > '9') | |
536 | break; | |
537 | s++; | |
538 | } | |
539 | if (*s == '\0') | |
540 | parse_uint(buf2, &id.numid); | |
541 | else { | |
542 | for (; *s >= ' '; s++); | |
543 | *s = '\0'; | |
544 | strlcpy(id.name, buf2, sizeof(id.name)); | |
545 | } | |
546 | break; | |
547 | } | |
548 | if (*s == ',') | |
549 | s++; | |
62327ebb JK |
550 | if (s == os) |
551 | break; | |
a135dfb5 JK |
552 | } |
553 | ||
554 | err = snd_ctl_led_set_id(led_card->number, &id, led_card->led->group, attach); | |
555 | if (err < 0) | |
556 | return err; | |
557 | ||
558 | return count; | |
559 | } | |
560 | ||
561 | static ssize_t parse_attach(struct device *dev, struct device_attribute *attr, | |
562 | const char *buf, size_t count) | |
563 | { | |
564 | struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); | |
565 | return set_led_id(led_card, buf, count, true); | |
566 | } | |
567 | ||
568 | static ssize_t parse_detach(struct device *dev, struct device_attribute *attr, | |
569 | const char *buf, size_t count) | |
570 | { | |
571 | struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); | |
572 | return set_led_id(led_card, buf, count, false); | |
573 | } | |
574 | ||
575 | static ssize_t ctl_reset(struct device *dev, 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 | int err; | |
580 | ||
581 | if (count > 0 && buf[0] == '1') { | |
582 | err = snd_ctl_led_reset(led_card->number, led_card->led->group); | |
583 | if (err < 0) | |
584 | return err; | |
585 | } | |
586 | return count; | |
587 | } | |
588 | ||
589 | static ssize_t ctl_list(struct device *dev, | |
590 | struct device_attribute *attr, char *buf) | |
591 | { | |
592 | struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); | |
593 | struct snd_card *card; | |
594 | struct snd_ctl_led_ctl *lctl; | |
595 | char *buf2 = buf; | |
596 | size_t l; | |
597 | ||
598 | card = snd_card_ref(led_card->number); | |
599 | if (!card) | |
600 | return -ENXIO; | |
601 | down_read(&card->controls_rwsem); | |
602 | mutex_lock(&snd_ctl_led_mutex); | |
603 | if (snd_ctl_led_card_valid[led_card->number]) { | |
604 | list_for_each_entry(lctl, &led_card->led->controls, list) | |
605 | if (lctl->card == card) { | |
606 | if (buf2 - buf > PAGE_SIZE - 16) | |
607 | break; | |
608 | if (buf2 != buf) | |
609 | *buf2++ = ' '; | |
610 | l = scnprintf(buf2, 15, "%u", | |
611 | lctl->kctl->id.numid + | |
612 | lctl->index_offset); | |
613 | buf2[l] = '\0'; | |
614 | buf2 += l + 1; | |
615 | } | |
616 | } | |
617 | mutex_unlock(&snd_ctl_led_mutex); | |
618 | up_read(&card->controls_rwsem); | |
619 | snd_card_unref(card); | |
620 | return buf2 - buf; | |
621 | } | |
622 | ||
623 | static DEVICE_ATTR(attach, 0200, NULL, parse_attach); | |
624 | static DEVICE_ATTR(detach, 0200, NULL, parse_detach); | |
625 | static DEVICE_ATTR(reset, 0200, NULL, ctl_reset); | |
626 | static DEVICE_ATTR(list, 0444, ctl_list, NULL); | |
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 | ||
cb17fe00 JK |
645 | static struct device snd_ctl_led_dev; |
646 | ||
a135dfb5 JK |
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(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 | if (dev_set_name(&led_card->dev, "card%d", card->number) < 0) | |
663 | goto cerr; | |
664 | led_card->dev.parent = &led->dev; | |
665 | led_card->dev.groups = snd_ctl_led_card_attr_groups; | |
666 | if (device_add(&led_card->dev)) | |
667 | goto cerr; | |
668 | led->cards[card->number] = led_card; | |
669 | snprintf(link_name, sizeof(link_name), "led-%s", led->name); | |
670 | WARN(sysfs_create_link(&card->ctl_dev.kobj, &led_card->dev.kobj, link_name), | |
671 | "can't create symlink to controlC%i device\n", card->number); | |
672 | WARN(sysfs_create_link(&led_card->dev.kobj, &card->card_dev.kobj, "card"), | |
673 | "can't create symlink to card%i\n", card->number); | |
674 | ||
675 | continue; | |
676 | cerr: | |
677 | put_device(&led_card->dev); | |
678 | cerr2: | |
679 | printk(KERN_ERR "snd_ctl_led: unable to add card%d", card->number); | |
680 | kfree(led_card); | |
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(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_del(&led_card->dev); | |
700 | kfree(led_card); | |
701 | led->cards[card->number] = NULL; | |
702 | } | |
703 | } | |
704 | ||
22d8de62 JK |
705 | /* |
706 | * Control layer registration | |
707 | */ | |
708 | static struct snd_ctl_layer_ops snd_ctl_led_lops = { | |
709 | .module_name = SND_CTL_LAYER_MODULE_LED, | |
710 | .lregister = snd_ctl_led_register, | |
711 | .ldisconnect = snd_ctl_led_disconnect, | |
712 | .lnotify = snd_ctl_led_notify, | |
713 | }; | |
714 | ||
715 | static int __init snd_ctl_led_init(void) | |
716 | { | |
cb17fe00 | 717 | struct snd_ctl_led *led; |
22d8de62 JK |
718 | unsigned int group; |
719 | ||
cb17fe00 JK |
720 | device_initialize(&snd_ctl_led_dev); |
721 | snd_ctl_led_dev.class = sound_class; | |
722 | dev_set_name(&snd_ctl_led_dev, "ctl-led"); | |
723 | if (device_add(&snd_ctl_led_dev)) { | |
724 | put_device(&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(&led->controls); | |
730 | device_initialize(&led->dev); | |
731 | led->dev.parent = &snd_ctl_led_dev; | |
732 | led->dev.groups = snd_ctl_led_dev_attr_groups; | |
733 | dev_set_name(&led->dev, led->name); | |
734 | if (device_add(&led->dev)) { | |
735 | put_device(&led->dev); | |
736 | for (; group > 0; group--) { | |
737 | led = &snd_ctl_leds[group]; | |
738 | device_del(&led->dev); | |
739 | } | |
740 | device_del(&snd_ctl_led_dev); | |
741 | return -ENOMEM; | |
742 | } | |
743 | } | |
22d8de62 JK |
744 | snd_ctl_register_layer(&snd_ctl_led_lops); |
745 | return 0; | |
746 | } | |
747 | ||
748 | static void __exit snd_ctl_led_exit(void) | |
749 | { | |
cb17fe00 | 750 | struct snd_ctl_led *led; |
a135dfb5 JK |
751 | struct snd_card *card; |
752 | unsigned int group, card_number; | |
cb17fe00 | 753 | |
a135dfb5 JK |
754 | snd_ctl_disconnect_layer(&snd_ctl_led_lops); |
755 | for (card_number = 0; card_number < SNDRV_CARDS; card_number++) { | |
756 | if (!snd_ctl_led_card_valid[card_number]) | |
757 | continue; | |
758 | card = snd_card_ref(card_number); | |
759 | if (card) { | |
760 | snd_ctl_led_sysfs_remove(card); | |
761 | snd_card_unref(card); | |
762 | } | |
763 | } | |
cb17fe00 JK |
764 | for (group = 0; group < MAX_LED; group++) { |
765 | led = &snd_ctl_leds[group]; | |
766 | device_del(&led->dev); | |
767 | } | |
768 | device_del(&snd_ctl_led_dev); | |
22d8de62 JK |
769 | snd_ctl_led_clean(NULL); |
770 | } | |
771 | ||
772 | module_init(snd_ctl_led_init) | |
773 | module_exit(snd_ctl_led_exit) |