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); | |
38ea4c3d | 257 | kctl = snd_ctl_find_id(card, id); |
7dba48a4 TI |
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; | |
541b8a26 | 292 | struct snd_ctl_led_ctl *lctl, *_lctl; |
cb17fe00 | 293 | struct snd_ctl_led *led; |
22d8de62 JK |
294 | |
295 | for (group = 0; group < MAX_LED; group++) { | |
cb17fe00 | 296 | led = &snd_ctl_leds[group]; |
541b8a26 AS |
297 | list_for_each_entry_safe(lctl, _lctl, &led->controls, list) |
298 | if (!card || lctl->card == card) | |
a135dfb5 | 299 | snd_ctl_led_ctl_destroy(lctl); |
22d8de62 JK |
300 | } |
301 | } | |
302 | ||
a135dfb5 JK |
303 | static int snd_ctl_led_reset(int card_number, unsigned int group) |
304 | { | |
7dba48a4 | 305 | struct snd_card *card __free(snd_card_unref) = NULL; |
541b8a26 | 306 | struct snd_ctl_led_ctl *lctl, *_lctl; |
a135dfb5 | 307 | struct snd_ctl_led *led; |
a135dfb5 JK |
308 | struct snd_kcontrol_volatile *vd; |
309 | bool change = false; | |
310 | ||
311 | card = snd_card_ref(card_number); | |
312 | if (!card) | |
313 | return -ENXIO; | |
314 | ||
7dba48a4 TI |
315 | scoped_guard(mutex, &snd_ctl_led_mutex) { |
316 | if (!snd_ctl_led_card_valid[card_number]) | |
317 | return -ENXIO; | |
318 | led = &snd_ctl_leds[group]; | |
541b8a26 | 319 | list_for_each_entry_safe(lctl, _lctl, &led->controls, list) |
7dba48a4 TI |
320 | if (lctl->card == card) { |
321 | vd = &lctl->kctl->vd[lctl->index_offset]; | |
322 | vd->access &= ~group_to_access(group); | |
323 | snd_ctl_led_ctl_destroy(lctl); | |
324 | change = true; | |
7dba48a4 TI |
325 | } |
326 | } | |
a135dfb5 JK |
327 | if (change) |
328 | snd_ctl_led_set_state(NULL, group_to_access(group), NULL, 0); | |
a135dfb5 JK |
329 | return 0; |
330 | } | |
331 | ||
22d8de62 JK |
332 | static void snd_ctl_led_register(struct snd_card *card) |
333 | { | |
334 | struct snd_kcontrol *kctl; | |
335 | unsigned int ioff; | |
336 | ||
337 | if (snd_BUG_ON(card->number < 0 || | |
338 | card->number >= ARRAY_SIZE(snd_ctl_led_card_valid))) | |
339 | return; | |
7dba48a4 TI |
340 | scoped_guard(mutex, &snd_ctl_led_mutex) |
341 | snd_ctl_led_card_valid[card->number] = true; | |
22d8de62 JK |
342 | /* the register callback is already called with held card->controls_rwsem */ |
343 | list_for_each_entry(kctl, &card->controls, list) | |
344 | for (ioff = 0; ioff < kctl->count; ioff++) | |
345 | snd_ctl_led_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, kctl, ioff); | |
346 | snd_ctl_led_refresh(); | |
a135dfb5 | 347 | snd_ctl_led_sysfs_add(card); |
22d8de62 JK |
348 | } |
349 | ||
350 | static void snd_ctl_led_disconnect(struct snd_card *card) | |
351 | { | |
a135dfb5 | 352 | snd_ctl_led_sysfs_remove(card); |
7dba48a4 TI |
353 | scoped_guard(mutex, &snd_ctl_led_mutex) { |
354 | snd_ctl_led_card_valid[card->number] = false; | |
355 | snd_ctl_led_clean(card); | |
356 | } | |
22d8de62 JK |
357 | snd_ctl_led_refresh(); |
358 | } | |
359 | ||
3ae72f6a DM |
360 | static void snd_ctl_led_card_release(struct device *dev) |
361 | { | |
362 | struct snd_ctl_led_card *led_card = to_led_card_dev(dev); | |
363 | ||
364 | kfree(led_card); | |
365 | } | |
366 | ||
367 | static void snd_ctl_led_release(struct device *dev) | |
368 | { | |
369 | } | |
370 | ||
371 | static void snd_ctl_led_dev_release(struct device *dev) | |
372 | { | |
373 | } | |
374 | ||
cb17fe00 JK |
375 | /* |
376 | * sysfs | |
377 | */ | |
378 | ||
08e767cd | 379 | static ssize_t mode_show(struct device *dev, |
cb17fe00 JK |
380 | struct device_attribute *attr, char *buf) |
381 | { | |
382 | struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); | |
e381a14c | 383 | const char *str = NULL; |
cb17fe00 JK |
384 | |
385 | switch (led->mode) { | |
386 | case MODE_FOLLOW_MUTE: str = "follow-mute"; break; | |
387 | case MODE_FOLLOW_ROUTE: str = "follow-route"; break; | |
388 | case MODE_ON: str = "on"; break; | |
389 | case MODE_OFF: str = "off"; break; | |
390 | } | |
ade79563 | 391 | return sysfs_emit(buf, "%s\n", str); |
cb17fe00 JK |
392 | } |
393 | ||
08e767cd Y |
394 | static ssize_t mode_store(struct device *dev, |
395 | struct device_attribute *attr, | |
cb17fe00 JK |
396 | const char *buf, size_t count) |
397 | { | |
398 | struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); | |
399 | char _buf[16]; | |
53cc2643 | 400 | size_t l = min(count, sizeof(_buf) - 1); |
cb17fe00 JK |
401 | enum snd_ctl_led_mode mode; |
402 | ||
403 | memcpy(_buf, buf, l); | |
404 | _buf[l] = '\0'; | |
405 | if (strstr(_buf, "mute")) | |
406 | mode = MODE_FOLLOW_MUTE; | |
407 | else if (strstr(_buf, "route")) | |
408 | mode = MODE_FOLLOW_ROUTE; | |
409 | else if (strncmp(_buf, "off", 3) == 0 || strncmp(_buf, "0", 1) == 0) | |
410 | mode = MODE_OFF; | |
411 | else if (strncmp(_buf, "on", 2) == 0 || strncmp(_buf, "1", 1) == 0) | |
412 | mode = MODE_ON; | |
413 | else | |
414 | return count; | |
415 | ||
7dba48a4 TI |
416 | scoped_guard(mutex, &snd_ctl_led_mutex) |
417 | led->mode = mode; | |
cb17fe00 JK |
418 | |
419 | snd_ctl_led_set_state(NULL, group_to_access(led->group), NULL, 0); | |
420 | return count; | |
421 | } | |
422 | ||
08e767cd | 423 | static ssize_t brightness_show(struct device *dev, |
cb17fe00 JK |
424 | struct device_attribute *attr, char *buf) |
425 | { | |
426 | struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); | |
a24de38d | 427 | struct led_trigger *trig = snd_ctl_ledtrig_audio[led->trigger_type]; |
cb17fe00 | 428 | |
a24de38d | 429 | return sysfs_emit(buf, "%u\n", led_trigger_get_brightness(trig)); |
cb17fe00 JK |
430 | } |
431 | ||
08e767cd Y |
432 | static DEVICE_ATTR_RW(mode); |
433 | static DEVICE_ATTR_RO(brightness); | |
cb17fe00 JK |
434 | |
435 | static struct attribute *snd_ctl_led_dev_attrs[] = { | |
436 | &dev_attr_mode.attr, | |
437 | &dev_attr_brightness.attr, | |
438 | NULL, | |
439 | }; | |
440 | ||
441 | static const struct attribute_group snd_ctl_led_dev_attr_group = { | |
442 | .attrs = snd_ctl_led_dev_attrs, | |
443 | }; | |
444 | ||
445 | static const struct attribute_group *snd_ctl_led_dev_attr_groups[] = { | |
446 | &snd_ctl_led_dev_attr_group, | |
447 | NULL, | |
448 | }; | |
449 | ||
a135dfb5 JK |
450 | static char *find_eos(char *s) |
451 | { | |
452 | while (*s && *s != ',') | |
453 | s++; | |
454 | if (*s) | |
455 | s++; | |
456 | return s; | |
457 | } | |
458 | ||
459 | static char *parse_uint(char *s, unsigned int *val) | |
460 | { | |
461 | unsigned long long res; | |
462 | if (kstrtoull(s, 10, &res)) | |
463 | res = 0; | |
464 | *val = res; | |
465 | return find_eos(s); | |
466 | } | |
467 | ||
468 | static char *parse_string(char *s, char *val, size_t val_size) | |
469 | { | |
470 | if (*s == '"' || *s == '\'') { | |
471 | char c = *s; | |
472 | s++; | |
473 | while (*s && *s != c) { | |
474 | if (val_size > 1) { | |
475 | *val++ = *s; | |
476 | val_size--; | |
477 | } | |
478 | s++; | |
479 | } | |
480 | } else { | |
481 | while (*s && *s != ',') { | |
482 | if (val_size > 1) { | |
483 | *val++ = *s; | |
484 | val_size--; | |
485 | } | |
486 | s++; | |
487 | } | |
488 | } | |
489 | *val = '\0'; | |
490 | if (*s) | |
491 | s++; | |
492 | return s; | |
493 | } | |
494 | ||
7c72665c | 495 | static char *parse_iface(char *s, snd_ctl_elem_iface_t *val) |
a135dfb5 JK |
496 | { |
497 | if (!strncasecmp(s, "card", 4)) | |
498 | *val = SNDRV_CTL_ELEM_IFACE_CARD; | |
499 | else if (!strncasecmp(s, "mixer", 5)) | |
500 | *val = SNDRV_CTL_ELEM_IFACE_MIXER; | |
501 | return find_eos(s); | |
502 | } | |
503 | ||
504 | /* | |
505 | * These types of input strings are accepted: | |
506 | * | |
507 | * unsigned integer - numid (equivaled to numid=UINT) | |
508 | * string - basic mixer name (equivalent to iface=MIXER,name=STR) | |
509 | * numid=UINT | |
510 | * [iface=MIXER,][device=UINT,][subdevice=UINT,]name=STR[,index=UINT] | |
511 | */ | |
512 | static ssize_t set_led_id(struct snd_ctl_led_card *led_card, const char *buf, size_t count, | |
513 | bool attach) | |
514 | { | |
62327ebb | 515 | char buf2[256], *s, *os; |
a135dfb5 JK |
516 | struct snd_ctl_elem_id id; |
517 | int err; | |
518 | ||
70051cff JK |
519 | if (strscpy(buf2, buf, sizeof(buf2)) < 0) |
520 | return -E2BIG; | |
a135dfb5 JK |
521 | memset(&id, 0, sizeof(id)); |
522 | id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; | |
523 | s = buf2; | |
524 | while (*s) { | |
62327ebb | 525 | os = s; |
a135dfb5 JK |
526 | if (!strncasecmp(s, "numid=", 6)) { |
527 | s = parse_uint(s + 6, &id.numid); | |
528 | } else if (!strncasecmp(s, "iface=", 6)) { | |
529 | s = parse_iface(s + 6, &id.iface); | |
530 | } else if (!strncasecmp(s, "device=", 7)) { | |
531 | s = parse_uint(s + 7, &id.device); | |
532 | } else if (!strncasecmp(s, "subdevice=", 10)) { | |
533 | s = parse_uint(s + 10, &id.subdevice); | |
534 | } else if (!strncasecmp(s, "name=", 5)) { | |
535 | s = parse_string(s + 5, id.name, sizeof(id.name)); | |
536 | } else if (!strncasecmp(s, "index=", 6)) { | |
537 | s = parse_uint(s + 6, &id.index); | |
538 | } else if (s == buf2) { | |
539 | while (*s) { | |
540 | if (*s < '0' || *s > '9') | |
541 | break; | |
542 | s++; | |
543 | } | |
544 | if (*s == '\0') | |
545 | parse_uint(buf2, &id.numid); | |
546 | else { | |
547 | for (; *s >= ' '; s++); | |
548 | *s = '\0'; | |
360a5812 | 549 | strscpy(id.name, buf2, sizeof(id.name)); |
a135dfb5 JK |
550 | } |
551 | break; | |
552 | } | |
553 | if (*s == ',') | |
554 | s++; | |
62327ebb JK |
555 | if (s == os) |
556 | break; | |
a135dfb5 JK |
557 | } |
558 | ||
559 | err = snd_ctl_led_set_id(led_card->number, &id, led_card->led->group, attach); | |
560 | if (err < 0) | |
561 | return err; | |
562 | ||
563 | return count; | |
564 | } | |
565 | ||
08e767cd Y |
566 | static ssize_t attach_store(struct device *dev, |
567 | struct device_attribute *attr, | |
a135dfb5 JK |
568 | const char *buf, size_t count) |
569 | { | |
570 | struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); | |
571 | return set_led_id(led_card, buf, count, true); | |
572 | } | |
573 | ||
08e767cd Y |
574 | static ssize_t detach_store(struct device *dev, |
575 | struct device_attribute *attr, | |
a135dfb5 JK |
576 | const char *buf, size_t count) |
577 | { | |
578 | struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); | |
579 | return set_led_id(led_card, buf, count, false); | |
580 | } | |
581 | ||
08e767cd Y |
582 | static ssize_t reset_store(struct device *dev, |
583 | struct device_attribute *attr, | |
584 | const char *buf, size_t count) | |
a135dfb5 JK |
585 | { |
586 | struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); | |
587 | int err; | |
588 | ||
589 | if (count > 0 && buf[0] == '1') { | |
590 | err = snd_ctl_led_reset(led_card->number, led_card->led->group); | |
591 | if (err < 0) | |
592 | return err; | |
593 | } | |
594 | return count; | |
595 | } | |
596 | ||
08e767cd Y |
597 | static ssize_t list_show(struct device *dev, |
598 | struct device_attribute *attr, char *buf) | |
a135dfb5 JK |
599 | { |
600 | struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); | |
7dba48a4 | 601 | struct snd_card *card __free(snd_card_unref) = NULL; |
a135dfb5 | 602 | struct snd_ctl_led_ctl *lctl; |
ade79563 | 603 | size_t l = 0; |
a135dfb5 JK |
604 | |
605 | card = snd_card_ref(led_card->number); | |
606 | if (!card) | |
607 | return -ENXIO; | |
7dba48a4 TI |
608 | guard(rwsem_read)(&card->controls_rwsem); |
609 | guard(mutex)(&snd_ctl_led_mutex); | |
a135dfb5 | 610 | if (snd_ctl_led_card_valid[led_card->number]) { |
ade79563 TI |
611 | list_for_each_entry(lctl, &led_card->led->controls, list) { |
612 | if (lctl->card != card) | |
613 | continue; | |
614 | if (l) | |
615 | l += sysfs_emit_at(buf, l, " "); | |
616 | l += sysfs_emit_at(buf, l, "%u", | |
617 | lctl->kctl->id.numid + lctl->index_offset); | |
618 | } | |
a135dfb5 | 619 | } |
ade79563 | 620 | return l; |
a135dfb5 JK |
621 | } |
622 | ||
08e767cd Y |
623 | static DEVICE_ATTR_WO(attach); |
624 | static DEVICE_ATTR_WO(detach); | |
625 | static DEVICE_ATTR_WO(reset); | |
626 | static DEVICE_ATTR_RO(list); | |
a135dfb5 JK |
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); | |
3ae72f6a | 662 | led_card->dev.release = snd_ctl_led_card_release; |
a135dfb5 JK |
663 | if (dev_set_name(&led_card->dev, "card%d", card->number) < 0) |
664 | goto cerr; | |
665 | led_card->dev.parent = &led->dev; | |
666 | led_card->dev.groups = snd_ctl_led_card_attr_groups; | |
667 | if (device_add(&led_card->dev)) | |
668 | goto cerr; | |
669 | led->cards[card->number] = led_card; | |
670 | snprintf(link_name, sizeof(link_name), "led-%s", led->name); | |
b2e538a9 TI |
671 | if (sysfs_create_link(&card->ctl_dev->kobj, &led_card->dev.kobj, |
672 | link_name)) | |
673 | dev_err(card->dev, | |
674 | "%s: can't create symlink to controlC%i device\n", | |
675 | __func__, card->number); | |
676 | if (sysfs_create_link(&led_card->dev.kobj, &card->card_dev.kobj, | |
677 | "card")) | |
678 | dev_err(card->dev, | |
679 | "%s: can't create symlink to card%i\n", | |
680 | __func__, card->number); | |
a135dfb5 JK |
681 | |
682 | continue; | |
683 | cerr: | |
684 | put_device(&led_card->dev); | |
685 | cerr2: | |
56887daf | 686 | dev_err(card->dev, "snd_ctl_led: unable to add card%d", card->number); |
a135dfb5 JK |
687 | } |
688 | } | |
689 | ||
690 | static void snd_ctl_led_sysfs_remove(struct snd_card *card) | |
691 | { | |
692 | unsigned int group; | |
693 | struct snd_ctl_led_card *led_card; | |
694 | struct snd_ctl_led *led; | |
695 | char link_name[32]; | |
696 | ||
697 | for (group = 0; group < MAX_LED; group++) { | |
698 | led = &snd_ctl_leds[group]; | |
699 | led_card = led->cards[card->number]; | |
700 | if (!led_card) | |
701 | continue; | |
702 | snprintf(link_name, sizeof(link_name), "led-%s", led->name); | |
6a66b01d | 703 | sysfs_remove_link(&card->ctl_dev->kobj, link_name); |
a135dfb5 | 704 | sysfs_remove_link(&led_card->dev.kobj, "card"); |
3ae72f6a | 705 | device_unregister(&led_card->dev); |
a135dfb5 JK |
706 | led->cards[card->number] = NULL; |
707 | } | |
708 | } | |
709 | ||
22d8de62 JK |
710 | /* |
711 | * Control layer registration | |
712 | */ | |
713 | static struct snd_ctl_layer_ops snd_ctl_led_lops = { | |
714 | .module_name = SND_CTL_LAYER_MODULE_LED, | |
715 | .lregister = snd_ctl_led_register, | |
716 | .ldisconnect = snd_ctl_led_disconnect, | |
717 | .lnotify = snd_ctl_led_notify, | |
718 | }; | |
719 | ||
720 | static int __init snd_ctl_led_init(void) | |
721 | { | |
cb17fe00 | 722 | struct snd_ctl_led *led; |
22d8de62 JK |
723 | unsigned int group; |
724 | ||
a24de38d HK |
725 | led_trigger_register_simple("audio-mute", &snd_ctl_ledtrig_audio[LED_AUDIO_MUTE]); |
726 | led_trigger_register_simple("audio-micmute", &snd_ctl_ledtrig_audio[LED_AUDIO_MICMUTE]); | |
727 | ||
cb17fe00 | 728 | device_initialize(&snd_ctl_led_dev); |
8d0cf150 | 729 | snd_ctl_led_dev.class = &sound_class; |
3ae72f6a | 730 | snd_ctl_led_dev.release = snd_ctl_led_dev_release; |
cb17fe00 JK |
731 | dev_set_name(&snd_ctl_led_dev, "ctl-led"); |
732 | if (device_add(&snd_ctl_led_dev)) { | |
733 | put_device(&snd_ctl_led_dev); | |
734 | return -ENOMEM; | |
735 | } | |
736 | for (group = 0; group < MAX_LED; group++) { | |
737 | led = &snd_ctl_leds[group]; | |
738 | INIT_LIST_HEAD(&led->controls); | |
739 | device_initialize(&led->dev); | |
740 | led->dev.parent = &snd_ctl_led_dev; | |
3ae72f6a | 741 | led->dev.release = snd_ctl_led_release; |
cb17fe00 JK |
742 | led->dev.groups = snd_ctl_led_dev_attr_groups; |
743 | dev_set_name(&led->dev, led->name); | |
744 | if (device_add(&led->dev)) { | |
745 | put_device(&led->dev); | |
746 | for (; group > 0; group--) { | |
57b138dd | 747 | led = &snd_ctl_leds[group - 1]; |
3ae72f6a | 748 | device_unregister(&led->dev); |
cb17fe00 | 749 | } |
3ae72f6a | 750 | device_unregister(&snd_ctl_led_dev); |
cb17fe00 JK |
751 | return -ENOMEM; |
752 | } | |
753 | } | |
22d8de62 JK |
754 | snd_ctl_register_layer(&snd_ctl_led_lops); |
755 | return 0; | |
756 | } | |
757 | ||
758 | static void __exit snd_ctl_led_exit(void) | |
759 | { | |
cb17fe00 | 760 | struct snd_ctl_led *led; |
a135dfb5 JK |
761 | struct snd_card *card; |
762 | unsigned int group, card_number; | |
cb17fe00 | 763 | |
a135dfb5 JK |
764 | snd_ctl_disconnect_layer(&snd_ctl_led_lops); |
765 | for (card_number = 0; card_number < SNDRV_CARDS; card_number++) { | |
766 | if (!snd_ctl_led_card_valid[card_number]) | |
767 | continue; | |
768 | card = snd_card_ref(card_number); | |
769 | if (card) { | |
770 | snd_ctl_led_sysfs_remove(card); | |
771 | snd_card_unref(card); | |
772 | } | |
773 | } | |
cb17fe00 JK |
774 | for (group = 0; group < MAX_LED; group++) { |
775 | led = &snd_ctl_leds[group]; | |
3ae72f6a | 776 | device_unregister(&led->dev); |
cb17fe00 | 777 | } |
3ae72f6a | 778 | device_unregister(&snd_ctl_led_dev); |
22d8de62 | 779 | snd_ctl_led_clean(NULL); |
a24de38d HK |
780 | |
781 | led_trigger_unregister_simple(snd_ctl_ledtrig_audio[LED_AUDIO_MUTE]); | |
782 | led_trigger_unregister_simple(snd_ctl_ledtrig_audio[LED_AUDIO_MICMUTE]); | |
22d8de62 JK |
783 | } |
784 | ||
785 | module_init(snd_ctl_led_init) | |
786 | module_exit(snd_ctl_led_exit) | |
a24de38d HK |
787 | |
788 | MODULE_ALIAS("ledtrig:audio-mute"); | |
789 | MODULE_ALIAS("ledtrig:audio-micmute"); |