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