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]; |
a24de38d | 56 | static struct led_trigger *snd_ctl_ledtrig_audio[NUM_AUDIO_LEDS]; |
cb17fe00 JK |
57 | static struct snd_ctl_led snd_ctl_leds[MAX_LED] = { |
58 | { | |
59 | .name = "speaker", | |
60 | .group = (SNDRV_CTL_ELEM_ACCESS_SPK_LED >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1, | |
61 | .trigger_type = LED_AUDIO_MUTE, | |
62 | .mode = MODE_FOLLOW_MUTE, | |
63 | }, | |
64 | { | |
65 | .name = "mic", | |
66 | .group = (SNDRV_CTL_ELEM_ACCESS_MIC_LED >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1, | |
67 | .trigger_type = LED_AUDIO_MICMUTE, | |
68 | .mode = MODE_FOLLOW_MUTE, | |
69 | }, | |
70 | }; | |
22d8de62 | 71 | |
a135dfb5 JK |
72 | static void snd_ctl_led_sysfs_add(struct snd_card *card); |
73 | static void snd_ctl_led_sysfs_remove(struct snd_card *card); | |
74 | ||
22d8de62 JK |
75 | #define UPDATE_ROUTE(route, cb) \ |
76 | do { \ | |
77 | int route2 = (cb); \ | |
78 | if (route2 >= 0) \ | |
79 | route = route < 0 ? route2 : (route | route2); \ | |
80 | } while (0) | |
81 | ||
82 | static inline unsigned int access_to_group(unsigned int access) | |
83 | { | |
84 | return ((access & SNDRV_CTL_ELEM_ACCESS_LED_MASK) >> | |
85 | SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1; | |
86 | } | |
87 | ||
88 | static inline unsigned int group_to_access(unsigned int group) | |
89 | { | |
90 | return (group + 1) << SNDRV_CTL_ELEM_ACCESS_LED_SHIFT; | |
91 | } | |
92 | ||
cb17fe00 | 93 | static struct snd_ctl_led *snd_ctl_led_get_by_access(unsigned int access) |
22d8de62 JK |
94 | { |
95 | unsigned int group = access_to_group(access); | |
96 | if (group >= MAX_LED) | |
97 | return NULL; | |
cb17fe00 | 98 | return &snd_ctl_leds[group]; |
22d8de62 JK |
99 | } |
100 | ||
543f8d78 JK |
101 | /* |
102 | * A note for callers: | |
103 | * The two static variables info and value are protected using snd_ctl_led_mutex. | |
104 | */ | |
cb17fe00 | 105 | static int snd_ctl_led_get(struct snd_ctl_led_ctl *lctl) |
22d8de62 | 106 | { |
543f8d78 JK |
107 | static struct snd_ctl_elem_info info; |
108 | static struct snd_ctl_elem_value value; | |
22d8de62 | 109 | struct snd_kcontrol *kctl = lctl->kctl; |
22d8de62 JK |
110 | unsigned int i; |
111 | int result; | |
112 | ||
113 | memset(&info, 0, sizeof(info)); | |
114 | info.id = kctl->id; | |
115 | info.id.index += lctl->index_offset; | |
116 | info.id.numid += lctl->index_offset; | |
117 | result = kctl->info(kctl, &info); | |
118 | if (result < 0) | |
119 | return -1; | |
120 | memset(&value, 0, sizeof(value)); | |
121 | value.id = info.id; | |
122 | result = kctl->get(kctl, &value); | |
123 | if (result < 0) | |
124 | return -1; | |
125 | if (info.type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || | |
126 | info.type == SNDRV_CTL_ELEM_TYPE_INTEGER) { | |
127 | for (i = 0; i < info.count; i++) | |
128 | if (value.value.integer.value[i] != info.value.integer.min) | |
129 | return 1; | |
130 | } else if (info.type == SNDRV_CTL_ELEM_TYPE_INTEGER64) { | |
131 | for (i = 0; i < info.count; i++) | |
132 | if (value.value.integer64.value[i] != info.value.integer64.min) | |
133 | return 1; | |
134 | } | |
135 | return 0; | |
136 | } | |
137 | ||
138 | static void snd_ctl_led_set_state(struct snd_card *card, unsigned int access, | |
139 | struct snd_kcontrol *kctl, unsigned int ioff) | |
140 | { | |
cb17fe00 JK |
141 | struct snd_ctl_led *led; |
142 | struct snd_ctl_led_ctl *lctl; | |
22d8de62 JK |
143 | int route; |
144 | bool found; | |
145 | ||
cb17fe00 JK |
146 | led = snd_ctl_led_get_by_access(access); |
147 | if (!led) | |
22d8de62 | 148 | return; |
22d8de62 JK |
149 | route = -1; |
150 | found = false; | |
7dba48a4 TI |
151 | scoped_guard(mutex, &snd_ctl_led_mutex) { |
152 | /* the card may not be registered (active) at this point */ | |
153 | if (card && !snd_ctl_led_card_valid[card->number]) | |
154 | return; | |
155 | list_for_each_entry(lctl, &led->controls, list) { | |
156 | if (lctl->kctl == kctl && lctl->index_offset == ioff) | |
157 | found = true; | |
22d8de62 JK |
158 | UPDATE_ROUTE(route, snd_ctl_led_get(lctl)); |
159 | } | |
7dba48a4 TI |
160 | if (!found && kctl && card) { |
161 | lctl = kzalloc(sizeof(*lctl), GFP_KERNEL); | |
162 | if (lctl) { | |
163 | lctl->card = card; | |
164 | lctl->access = access; | |
165 | lctl->kctl = kctl; | |
166 | lctl->index_offset = ioff; | |
167 | list_add(&lctl->list, &led->controls); | |
168 | UPDATE_ROUTE(route, snd_ctl_led_get(lctl)); | |
169 | } | |
170 | } | |
22d8de62 | 171 | } |
cb17fe00 JK |
172 | switch (led->mode) { |
173 | case MODE_OFF: route = 1; break; | |
174 | case MODE_ON: route = 0; break; | |
175 | case MODE_FOLLOW_ROUTE: if (route >= 0) route ^= 1; break; | |
176 | case MODE_FOLLOW_MUTE: /* noop */ break; | |
177 | } | |
a24de38d HK |
178 | if (route >= 0) { |
179 | struct led_trigger *trig = snd_ctl_ledtrig_audio[led->trigger_type]; | |
180 | ||
181 | led_trigger_event(trig, route ? LED_OFF : LED_ON); | |
182 | } | |
22d8de62 JK |
183 | } |
184 | ||
cb17fe00 | 185 | static struct snd_ctl_led_ctl *snd_ctl_led_find(struct snd_kcontrol *kctl, unsigned int ioff) |
22d8de62 JK |
186 | { |
187 | struct list_head *controls; | |
cb17fe00 | 188 | struct snd_ctl_led_ctl *lctl; |
22d8de62 JK |
189 | unsigned int group; |
190 | ||
191 | for (group = 0; group < MAX_LED; group++) { | |
cb17fe00 | 192 | controls = &snd_ctl_leds[group].controls; |
22d8de62 JK |
193 | list_for_each_entry(lctl, controls, list) |
194 | if (lctl->kctl == kctl && lctl->index_offset == ioff) | |
195 | return lctl; | |
196 | } | |
197 | return NULL; | |
198 | } | |
199 | ||
200 | static unsigned int snd_ctl_led_remove(struct snd_kcontrol *kctl, unsigned int ioff, | |
201 | unsigned int access) | |
202 | { | |
cb17fe00 | 203 | struct snd_ctl_led_ctl *lctl; |
22d8de62 JK |
204 | unsigned int ret = 0; |
205 | ||
7dba48a4 | 206 | guard(mutex)(&snd_ctl_led_mutex); |
22d8de62 JK |
207 | lctl = snd_ctl_led_find(kctl, ioff); |
208 | if (lctl && (access == 0 || access != lctl->access)) { | |
209 | ret = lctl->access; | |
210 | list_del(&lctl->list); | |
211 | kfree(lctl); | |
212 | } | |
22d8de62 JK |
213 | return ret; |
214 | } | |
215 | ||
216 | static void snd_ctl_led_notify(struct snd_card *card, unsigned int mask, | |
217 | struct snd_kcontrol *kctl, unsigned int ioff) | |
218 | { | |
219 | struct snd_kcontrol_volatile *vd; | |
220 | unsigned int access, access2; | |
221 | ||
222 | if (mask == SNDRV_CTL_EVENT_MASK_REMOVE) { | |
223 | access = snd_ctl_led_remove(kctl, ioff, 0); | |
224 | if (access) | |
225 | snd_ctl_led_set_state(card, access, NULL, 0); | |
226 | } else if (mask & SNDRV_CTL_EVENT_MASK_INFO) { | |
227 | vd = &kctl->vd[ioff]; | |
228 | access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; | |
229 | access2 = snd_ctl_led_remove(kctl, ioff, access); | |
230 | if (access2) | |
231 | snd_ctl_led_set_state(card, access2, NULL, 0); | |
232 | if (access) | |
233 | snd_ctl_led_set_state(card, access, kctl, ioff); | |
234 | } else if ((mask & (SNDRV_CTL_EVENT_MASK_ADD | | |
235 | SNDRV_CTL_EVENT_MASK_VALUE)) != 0) { | |
236 | vd = &kctl->vd[ioff]; | |
237 | access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; | |
238 | if (access) | |
239 | snd_ctl_led_set_state(card, access, kctl, ioff); | |
240 | } | |
241 | } | |
242 | ||
7dba48a4 TI |
243 | DEFINE_FREE(snd_card_unref, struct snd_card *, if (_T) snd_card_unref(_T)) |
244 | ||
a135dfb5 JK |
245 | static int snd_ctl_led_set_id(int card_number, struct snd_ctl_elem_id *id, |
246 | unsigned int group, bool set) | |
247 | { | |
7dba48a4 | 248 | struct snd_card *card __free(snd_card_unref) = NULL; |
a135dfb5 JK |
249 | struct snd_kcontrol *kctl; |
250 | struct snd_kcontrol_volatile *vd; | |
251 | unsigned int ioff, access, new_access; | |
a135dfb5 JK |
252 | |
253 | card = snd_card_ref(card_number); | |
7dba48a4 TI |
254 | if (!card) |
255 | return -ENXIO; | |
256 | guard(rwsem_write)(&card->controls_rwsem); | |
257 | kctl = snd_ctl_find_id_locked(card, id); | |
258 | if (!kctl) | |
259 | return -ENOENT; | |
260 | ioff = snd_ctl_get_ioff(kctl, id); | |
261 | vd = &kctl->vd[ioff]; | |
262 | access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; | |
263 | if (access != 0 && access != group_to_access(group)) | |
264 | return -EXDEV; | |
265 | new_access = vd->access & ~SNDRV_CTL_ELEM_ACCESS_LED_MASK; | |
266 | if (set) | |
267 | new_access |= group_to_access(group); | |
268 | if (new_access != vd->access) { | |
269 | vd->access = new_access; | |
270 | snd_ctl_led_notify(card, SNDRV_CTL_EVENT_MASK_INFO, kctl, ioff); | |
a135dfb5 | 271 | } |
7dba48a4 | 272 | return 0; |
a135dfb5 JK |
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 | { | |
7dba48a4 | 308 | struct snd_card *card __free(snd_card_unref) = NULL; |
a135dfb5 JK |
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 | ||
7dba48a4 TI |
318 | scoped_guard(mutex, &snd_ctl_led_mutex) { |
319 | if (!snd_ctl_led_card_valid[card_number]) | |
320 | return -ENXIO; | |
321 | led = &snd_ctl_leds[group]; | |
a135dfb5 | 322 | repeat: |
7dba48a4 TI |
323 | list_for_each_entry(lctl, &led->controls, list) |
324 | if (lctl->card == card) { | |
325 | vd = &lctl->kctl->vd[lctl->index_offset]; | |
326 | vd->access &= ~group_to_access(group); | |
327 | snd_ctl_led_ctl_destroy(lctl); | |
328 | change = true; | |
329 | goto repeat; | |
330 | } | |
331 | } | |
a135dfb5 JK |
332 | if (change) |
333 | snd_ctl_led_set_state(NULL, group_to_access(group), NULL, 0); | |
a135dfb5 JK |
334 | return 0; |
335 | } | |
336 | ||
22d8de62 JK |
337 | static void snd_ctl_led_register(struct snd_card *card) |
338 | { | |
339 | struct snd_kcontrol *kctl; | |
340 | unsigned int ioff; | |
341 | ||
342 | if (snd_BUG_ON(card->number < 0 || | |
343 | card->number >= ARRAY_SIZE(snd_ctl_led_card_valid))) | |
344 | return; | |
7dba48a4 TI |
345 | scoped_guard(mutex, &snd_ctl_led_mutex) |
346 | snd_ctl_led_card_valid[card->number] = true; | |
22d8de62 JK |
347 | /* the register callback is already called with held card->controls_rwsem */ |
348 | list_for_each_entry(kctl, &card->controls, list) | |
349 | for (ioff = 0; ioff < kctl->count; ioff++) | |
350 | snd_ctl_led_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, kctl, ioff); | |
351 | snd_ctl_led_refresh(); | |
a135dfb5 | 352 | snd_ctl_led_sysfs_add(card); |
22d8de62 JK |
353 | } |
354 | ||
355 | static void snd_ctl_led_disconnect(struct snd_card *card) | |
356 | { | |
a135dfb5 | 357 | snd_ctl_led_sysfs_remove(card); |
7dba48a4 TI |
358 | scoped_guard(mutex, &snd_ctl_led_mutex) { |
359 | snd_ctl_led_card_valid[card->number] = false; | |
360 | snd_ctl_led_clean(card); | |
361 | } | |
22d8de62 JK |
362 | snd_ctl_led_refresh(); |
363 | } | |
364 | ||
3ae72f6a DM |
365 | static void snd_ctl_led_card_release(struct device *dev) |
366 | { | |
367 | struct snd_ctl_led_card *led_card = to_led_card_dev(dev); | |
368 | ||
369 | kfree(led_card); | |
370 | } | |
371 | ||
372 | static void snd_ctl_led_release(struct device *dev) | |
373 | { | |
374 | } | |
375 | ||
376 | static void snd_ctl_led_dev_release(struct device *dev) | |
377 | { | |
378 | } | |
379 | ||
cb17fe00 JK |
380 | /* |
381 | * sysfs | |
382 | */ | |
383 | ||
08e767cd | 384 | static ssize_t mode_show(struct device *dev, |
cb17fe00 JK |
385 | struct device_attribute *attr, char *buf) |
386 | { | |
387 | struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); | |
e381a14c | 388 | const char *str = NULL; |
cb17fe00 JK |
389 | |
390 | switch (led->mode) { | |
391 | case MODE_FOLLOW_MUTE: str = "follow-mute"; break; | |
392 | case MODE_FOLLOW_ROUTE: str = "follow-route"; break; | |
393 | case MODE_ON: str = "on"; break; | |
394 | case MODE_OFF: str = "off"; break; | |
395 | } | |
ade79563 | 396 | return sysfs_emit(buf, "%s\n", str); |
cb17fe00 JK |
397 | } |
398 | ||
08e767cd Y |
399 | static ssize_t mode_store(struct device *dev, |
400 | struct device_attribute *attr, | |
cb17fe00 JK |
401 | const char *buf, size_t count) |
402 | { | |
403 | struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); | |
404 | char _buf[16]; | |
53cc2643 | 405 | size_t l = min(count, sizeof(_buf) - 1); |
cb17fe00 JK |
406 | enum snd_ctl_led_mode mode; |
407 | ||
408 | memcpy(_buf, buf, l); | |
409 | _buf[l] = '\0'; | |
410 | if (strstr(_buf, "mute")) | |
411 | mode = MODE_FOLLOW_MUTE; | |
412 | else if (strstr(_buf, "route")) | |
413 | mode = MODE_FOLLOW_ROUTE; | |
414 | else if (strncmp(_buf, "off", 3) == 0 || strncmp(_buf, "0", 1) == 0) | |
415 | mode = MODE_OFF; | |
416 | else if (strncmp(_buf, "on", 2) == 0 || strncmp(_buf, "1", 1) == 0) | |
417 | mode = MODE_ON; | |
418 | else | |
419 | return count; | |
420 | ||
7dba48a4 TI |
421 | scoped_guard(mutex, &snd_ctl_led_mutex) |
422 | led->mode = mode; | |
cb17fe00 JK |
423 | |
424 | snd_ctl_led_set_state(NULL, group_to_access(led->group), NULL, 0); | |
425 | return count; | |
426 | } | |
427 | ||
08e767cd | 428 | static ssize_t brightness_show(struct device *dev, |
cb17fe00 JK |
429 | struct device_attribute *attr, char *buf) |
430 | { | |
431 | struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); | |
a24de38d | 432 | struct led_trigger *trig = snd_ctl_ledtrig_audio[led->trigger_type]; |
cb17fe00 | 433 | |
a24de38d | 434 | return sysfs_emit(buf, "%u\n", led_trigger_get_brightness(trig)); |
cb17fe00 JK |
435 | } |
436 | ||
08e767cd Y |
437 | static DEVICE_ATTR_RW(mode); |
438 | static DEVICE_ATTR_RO(brightness); | |
cb17fe00 JK |
439 | |
440 | static struct attribute *snd_ctl_led_dev_attrs[] = { | |
441 | &dev_attr_mode.attr, | |
442 | &dev_attr_brightness.attr, | |
443 | NULL, | |
444 | }; | |
445 | ||
446 | static const struct attribute_group snd_ctl_led_dev_attr_group = { | |
447 | .attrs = snd_ctl_led_dev_attrs, | |
448 | }; | |
449 | ||
450 | static const struct attribute_group *snd_ctl_led_dev_attr_groups[] = { | |
451 | &snd_ctl_led_dev_attr_group, | |
452 | NULL, | |
453 | }; | |
454 | ||
a135dfb5 JK |
455 | static char *find_eos(char *s) |
456 | { | |
457 | while (*s && *s != ',') | |
458 | s++; | |
459 | if (*s) | |
460 | s++; | |
461 | return s; | |
462 | } | |
463 | ||
464 | static char *parse_uint(char *s, unsigned int *val) | |
465 | { | |
466 | unsigned long long res; | |
467 | if (kstrtoull(s, 10, &res)) | |
468 | res = 0; | |
469 | *val = res; | |
470 | return find_eos(s); | |
471 | } | |
472 | ||
473 | static char *parse_string(char *s, char *val, size_t val_size) | |
474 | { | |
475 | if (*s == '"' || *s == '\'') { | |
476 | char c = *s; | |
477 | s++; | |
478 | while (*s && *s != c) { | |
479 | if (val_size > 1) { | |
480 | *val++ = *s; | |
481 | val_size--; | |
482 | } | |
483 | s++; | |
484 | } | |
485 | } else { | |
486 | while (*s && *s != ',') { | |
487 | if (val_size > 1) { | |
488 | *val++ = *s; | |
489 | val_size--; | |
490 | } | |
491 | s++; | |
492 | } | |
493 | } | |
494 | *val = '\0'; | |
495 | if (*s) | |
496 | s++; | |
497 | return s; | |
498 | } | |
499 | ||
7c72665c | 500 | static char *parse_iface(char *s, snd_ctl_elem_iface_t *val) |
a135dfb5 JK |
501 | { |
502 | if (!strncasecmp(s, "card", 4)) | |
503 | *val = SNDRV_CTL_ELEM_IFACE_CARD; | |
504 | else if (!strncasecmp(s, "mixer", 5)) | |
505 | *val = SNDRV_CTL_ELEM_IFACE_MIXER; | |
506 | return find_eos(s); | |
507 | } | |
508 | ||
509 | /* | |
510 | * These types of input strings are accepted: | |
511 | * | |
512 | * unsigned integer - numid (equivaled to numid=UINT) | |
513 | * string - basic mixer name (equivalent to iface=MIXER,name=STR) | |
514 | * numid=UINT | |
515 | * [iface=MIXER,][device=UINT,][subdevice=UINT,]name=STR[,index=UINT] | |
516 | */ | |
517 | static ssize_t set_led_id(struct snd_ctl_led_card *led_card, const char *buf, size_t count, | |
518 | bool attach) | |
519 | { | |
62327ebb | 520 | char buf2[256], *s, *os; |
a135dfb5 JK |
521 | struct snd_ctl_elem_id id; |
522 | int err; | |
523 | ||
70051cff JK |
524 | if (strscpy(buf2, buf, sizeof(buf2)) < 0) |
525 | return -E2BIG; | |
a135dfb5 JK |
526 | memset(&id, 0, sizeof(id)); |
527 | id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; | |
528 | s = buf2; | |
529 | while (*s) { | |
62327ebb | 530 | os = s; |
a135dfb5 JK |
531 | if (!strncasecmp(s, "numid=", 6)) { |
532 | s = parse_uint(s + 6, &id.numid); | |
533 | } else if (!strncasecmp(s, "iface=", 6)) { | |
534 | s = parse_iface(s + 6, &id.iface); | |
535 | } else if (!strncasecmp(s, "device=", 7)) { | |
536 | s = parse_uint(s + 7, &id.device); | |
537 | } else if (!strncasecmp(s, "subdevice=", 10)) { | |
538 | s = parse_uint(s + 10, &id.subdevice); | |
539 | } else if (!strncasecmp(s, "name=", 5)) { | |
540 | s = parse_string(s + 5, id.name, sizeof(id.name)); | |
541 | } else if (!strncasecmp(s, "index=", 6)) { | |
542 | s = parse_uint(s + 6, &id.index); | |
543 | } else if (s == buf2) { | |
544 | while (*s) { | |
545 | if (*s < '0' || *s > '9') | |
546 | break; | |
547 | s++; | |
548 | } | |
549 | if (*s == '\0') | |
550 | parse_uint(buf2, &id.numid); | |
551 | else { | |
552 | for (; *s >= ' '; s++); | |
553 | *s = '\0'; | |
360a5812 | 554 | strscpy(id.name, buf2, sizeof(id.name)); |
a135dfb5 JK |
555 | } |
556 | break; | |
557 | } | |
558 | if (*s == ',') | |
559 | s++; | |
62327ebb JK |
560 | if (s == os) |
561 | break; | |
a135dfb5 JK |
562 | } |
563 | ||
564 | err = snd_ctl_led_set_id(led_card->number, &id, led_card->led->group, attach); | |
565 | if (err < 0) | |
566 | return err; | |
567 | ||
568 | return count; | |
569 | } | |
570 | ||
08e767cd Y |
571 | static ssize_t attach_store(struct device *dev, |
572 | struct device_attribute *attr, | |
a135dfb5 JK |
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, true); | |
577 | } | |
578 | ||
08e767cd Y |
579 | static ssize_t detach_store(struct device *dev, |
580 | struct device_attribute *attr, | |
a135dfb5 JK |
581 | const char *buf, size_t count) |
582 | { | |
583 | struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); | |
584 | return set_led_id(led_card, buf, count, false); | |
585 | } | |
586 | ||
08e767cd Y |
587 | static ssize_t reset_store(struct device *dev, |
588 | struct device_attribute *attr, | |
589 | const char *buf, size_t count) | |
a135dfb5 JK |
590 | { |
591 | struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); | |
592 | int err; | |
593 | ||
594 | if (count > 0 && buf[0] == '1') { | |
595 | err = snd_ctl_led_reset(led_card->number, led_card->led->group); | |
596 | if (err < 0) | |
597 | return err; | |
598 | } | |
599 | return count; | |
600 | } | |
601 | ||
08e767cd Y |
602 | static ssize_t list_show(struct device *dev, |
603 | struct device_attribute *attr, char *buf) | |
a135dfb5 JK |
604 | { |
605 | struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); | |
7dba48a4 | 606 | struct snd_card *card __free(snd_card_unref) = NULL; |
a135dfb5 | 607 | struct snd_ctl_led_ctl *lctl; |
ade79563 | 608 | size_t l = 0; |
a135dfb5 JK |
609 | |
610 | card = snd_card_ref(led_card->number); | |
611 | if (!card) | |
612 | return -ENXIO; | |
7dba48a4 TI |
613 | guard(rwsem_read)(&card->controls_rwsem); |
614 | guard(mutex)(&snd_ctl_led_mutex); | |
a135dfb5 | 615 | if (snd_ctl_led_card_valid[led_card->number]) { |
ade79563 TI |
616 | list_for_each_entry(lctl, &led_card->led->controls, list) { |
617 | if (lctl->card != card) | |
618 | continue; | |
619 | if (l) | |
620 | l += sysfs_emit_at(buf, l, " "); | |
621 | l += sysfs_emit_at(buf, l, "%u", | |
622 | lctl->kctl->id.numid + lctl->index_offset); | |
623 | } | |
a135dfb5 | 624 | } |
ade79563 | 625 | return l; |
a135dfb5 JK |
626 | } |
627 | ||
08e767cd Y |
628 | static DEVICE_ATTR_WO(attach); |
629 | static DEVICE_ATTR_WO(detach); | |
630 | static DEVICE_ATTR_WO(reset); | |
631 | static DEVICE_ATTR_RO(list); | |
a135dfb5 JK |
632 | |
633 | static struct attribute *snd_ctl_led_card_attrs[] = { | |
634 | &dev_attr_attach.attr, | |
635 | &dev_attr_detach.attr, | |
636 | &dev_attr_reset.attr, | |
637 | &dev_attr_list.attr, | |
638 | NULL, | |
639 | }; | |
640 | ||
641 | static const struct attribute_group snd_ctl_led_card_attr_group = { | |
642 | .attrs = snd_ctl_led_card_attrs, | |
643 | }; | |
644 | ||
645 | static const struct attribute_group *snd_ctl_led_card_attr_groups[] = { | |
646 | &snd_ctl_led_card_attr_group, | |
647 | NULL, | |
648 | }; | |
649 | ||
cb17fe00 JK |
650 | static struct device snd_ctl_led_dev; |
651 | ||
a135dfb5 JK |
652 | static void snd_ctl_led_sysfs_add(struct snd_card *card) |
653 | { | |
654 | unsigned int group; | |
655 | struct snd_ctl_led_card *led_card; | |
656 | struct snd_ctl_led *led; | |
657 | char link_name[32]; | |
658 | ||
659 | for (group = 0; group < MAX_LED; group++) { | |
660 | led = &snd_ctl_leds[group]; | |
661 | led_card = kzalloc(sizeof(*led_card), GFP_KERNEL); | |
662 | if (!led_card) | |
663 | goto cerr2; | |
664 | led_card->number = card->number; | |
665 | led_card->led = led; | |
666 | device_initialize(&led_card->dev); | |
3ae72f6a | 667 | led_card->dev.release = snd_ctl_led_card_release; |
a135dfb5 JK |
668 | if (dev_set_name(&led_card->dev, "card%d", card->number) < 0) |
669 | goto cerr; | |
670 | led_card->dev.parent = &led->dev; | |
671 | led_card->dev.groups = snd_ctl_led_card_attr_groups; | |
672 | if (device_add(&led_card->dev)) | |
673 | goto cerr; | |
674 | led->cards[card->number] = led_card; | |
675 | snprintf(link_name, sizeof(link_name), "led-%s", led->name); | |
6a66b01d | 676 | WARN(sysfs_create_link(&card->ctl_dev->kobj, &led_card->dev.kobj, link_name), |
a135dfb5 JK |
677 | "can't create symlink to controlC%i device\n", card->number); |
678 | WARN(sysfs_create_link(&led_card->dev.kobj, &card->card_dev.kobj, "card"), | |
679 | "can't create symlink to card%i\n", card->number); | |
680 | ||
681 | continue; | |
682 | cerr: | |
683 | put_device(&led_card->dev); | |
684 | cerr2: | |
685 | printk(KERN_ERR "snd_ctl_led: unable to add card%d", card->number); | |
a135dfb5 JK |
686 | } |
687 | } | |
688 | ||
689 | static void snd_ctl_led_sysfs_remove(struct snd_card *card) | |
690 | { | |
691 | unsigned int group; | |
692 | struct snd_ctl_led_card *led_card; | |
693 | struct snd_ctl_led *led; | |
694 | char link_name[32]; | |
695 | ||
696 | for (group = 0; group < MAX_LED; group++) { | |
697 | led = &snd_ctl_leds[group]; | |
698 | led_card = led->cards[card->number]; | |
699 | if (!led_card) | |
700 | continue; | |
701 | snprintf(link_name, sizeof(link_name), "led-%s", led->name); | |
6a66b01d | 702 | sysfs_remove_link(&card->ctl_dev->kobj, link_name); |
a135dfb5 | 703 | sysfs_remove_link(&led_card->dev.kobj, "card"); |
3ae72f6a | 704 | device_unregister(&led_card->dev); |
a135dfb5 JK |
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 | ||
a24de38d HK |
724 | led_trigger_register_simple("audio-mute", &snd_ctl_ledtrig_audio[LED_AUDIO_MUTE]); |
725 | led_trigger_register_simple("audio-micmute", &snd_ctl_ledtrig_audio[LED_AUDIO_MICMUTE]); | |
726 | ||
cb17fe00 | 727 | device_initialize(&snd_ctl_led_dev); |
8d0cf150 | 728 | snd_ctl_led_dev.class = &sound_class; |
3ae72f6a | 729 | snd_ctl_led_dev.release = snd_ctl_led_dev_release; |
cb17fe00 JK |
730 | dev_set_name(&snd_ctl_led_dev, "ctl-led"); |
731 | if (device_add(&snd_ctl_led_dev)) { | |
732 | put_device(&snd_ctl_led_dev); | |
733 | return -ENOMEM; | |
734 | } | |
735 | for (group = 0; group < MAX_LED; group++) { | |
736 | led = &snd_ctl_leds[group]; | |
737 | INIT_LIST_HEAD(&led->controls); | |
738 | device_initialize(&led->dev); | |
739 | led->dev.parent = &snd_ctl_led_dev; | |
3ae72f6a | 740 | led->dev.release = snd_ctl_led_release; |
cb17fe00 JK |
741 | led->dev.groups = snd_ctl_led_dev_attr_groups; |
742 | dev_set_name(&led->dev, led->name); | |
743 | if (device_add(&led->dev)) { | |
744 | put_device(&led->dev); | |
745 | for (; group > 0; group--) { | |
57b138dd | 746 | led = &snd_ctl_leds[group - 1]; |
3ae72f6a | 747 | device_unregister(&led->dev); |
cb17fe00 | 748 | } |
3ae72f6a | 749 | device_unregister(&snd_ctl_led_dev); |
cb17fe00 JK |
750 | return -ENOMEM; |
751 | } | |
752 | } | |
22d8de62 JK |
753 | snd_ctl_register_layer(&snd_ctl_led_lops); |
754 | return 0; | |
755 | } | |
756 | ||
757 | static void __exit snd_ctl_led_exit(void) | |
758 | { | |
cb17fe00 | 759 | struct snd_ctl_led *led; |
a135dfb5 JK |
760 | struct snd_card *card; |
761 | unsigned int group, card_number; | |
cb17fe00 | 762 | |
a135dfb5 JK |
763 | snd_ctl_disconnect_layer(&snd_ctl_led_lops); |
764 | for (card_number = 0; card_number < SNDRV_CARDS; card_number++) { | |
765 | if (!snd_ctl_led_card_valid[card_number]) | |
766 | continue; | |
767 | card = snd_card_ref(card_number); | |
768 | if (card) { | |
769 | snd_ctl_led_sysfs_remove(card); | |
770 | snd_card_unref(card); | |
771 | } | |
772 | } | |
cb17fe00 JK |
773 | for (group = 0; group < MAX_LED; group++) { |
774 | led = &snd_ctl_leds[group]; | |
3ae72f6a | 775 | device_unregister(&led->dev); |
cb17fe00 | 776 | } |
3ae72f6a | 777 | device_unregister(&snd_ctl_led_dev); |
22d8de62 | 778 | snd_ctl_led_clean(NULL); |
a24de38d HK |
779 | |
780 | led_trigger_unregister_simple(snd_ctl_ledtrig_audio[LED_AUDIO_MUTE]); | |
781 | led_trigger_unregister_simple(snd_ctl_ledtrig_audio[LED_AUDIO_MICMUTE]); | |
22d8de62 JK |
782 | } |
783 | ||
784 | module_init(snd_ctl_led_init) | |
785 | module_exit(snd_ctl_led_exit) | |
a24de38d HK |
786 | |
787 | MODULE_ALIAS("ledtrig:audio-mute"); | |
788 | MODULE_ALIAS("ledtrig:audio-micmute"); |