122d8de62SJaroslav Kysela // SPDX-License-Identifier: GPL-2.0-or-later
222d8de62SJaroslav Kysela /*
322d8de62SJaroslav Kysela * LED state routines for driver control interface
422d8de62SJaroslav Kysela * Copyright (c) 2021 by Jaroslav Kysela <perex@perex.cz>
522d8de62SJaroslav Kysela */
622d8de62SJaroslav Kysela
722d8de62SJaroslav Kysela #include <linux/slab.h>
822d8de62SJaroslav Kysela #include <linux/module.h>
922d8de62SJaroslav Kysela #include <linux/leds.h>
1022d8de62SJaroslav Kysela #include <sound/core.h>
1122d8de62SJaroslav Kysela #include <sound/control.h>
1222d8de62SJaroslav Kysela
1322d8de62SJaroslav Kysela MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
1422d8de62SJaroslav Kysela MODULE_DESCRIPTION("ALSA control interface to LED trigger code.");
1522d8de62SJaroslav Kysela MODULE_LICENSE("GPL");
1622d8de62SJaroslav Kysela
1722d8de62SJaroslav Kysela #define MAX_LED (((SNDRV_CTL_ELEM_ACCESS_MIC_LED - SNDRV_CTL_ELEM_ACCESS_SPK_LED) \
1822d8de62SJaroslav Kysela >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) + 1)
1922d8de62SJaroslav Kysela
203ae72f6aSDongliang Mu #define to_led_card_dev(_dev) \
213ae72f6aSDongliang Mu container_of(_dev, struct snd_ctl_led_card, dev)
223ae72f6aSDongliang Mu
23cb17fe00SJaroslav Kysela enum snd_ctl_led_mode {
24cb17fe00SJaroslav Kysela MODE_FOLLOW_MUTE = 0,
25cb17fe00SJaroslav Kysela MODE_FOLLOW_ROUTE,
26cb17fe00SJaroslav Kysela MODE_OFF,
27cb17fe00SJaroslav Kysela MODE_ON,
28cb17fe00SJaroslav Kysela };
29cb17fe00SJaroslav Kysela
30a135dfb5SJaroslav Kysela struct snd_ctl_led_card {
31a135dfb5SJaroslav Kysela struct device dev;
32a135dfb5SJaroslav Kysela int number;
33a135dfb5SJaroslav Kysela struct snd_ctl_led *led;
34a135dfb5SJaroslav Kysela };
35a135dfb5SJaroslav Kysela
3622d8de62SJaroslav Kysela struct snd_ctl_led {
37cb17fe00SJaroslav Kysela struct device dev;
38cb17fe00SJaroslav Kysela struct list_head controls;
39cb17fe00SJaroslav Kysela const char *name;
40cb17fe00SJaroslav Kysela unsigned int group;
41cb17fe00SJaroslav Kysela enum led_audio trigger_type;
42cb17fe00SJaroslav Kysela enum snd_ctl_led_mode mode;
43a135dfb5SJaroslav Kysela struct snd_ctl_led_card *cards[SNDRV_CARDS];
44cb17fe00SJaroslav Kysela };
45cb17fe00SJaroslav Kysela
46cb17fe00SJaroslav Kysela struct snd_ctl_led_ctl {
4722d8de62SJaroslav Kysela struct list_head list;
4822d8de62SJaroslav Kysela struct snd_card *card;
4922d8de62SJaroslav Kysela unsigned int access;
5022d8de62SJaroslav Kysela struct snd_kcontrol *kctl;
5122d8de62SJaroslav Kysela unsigned int index_offset;
5222d8de62SJaroslav Kysela };
5322d8de62SJaroslav Kysela
5422d8de62SJaroslav Kysela static DEFINE_MUTEX(snd_ctl_led_mutex);
5522d8de62SJaroslav Kysela static bool snd_ctl_led_card_valid[SNDRV_CARDS];
56cb17fe00SJaroslav Kysela static struct snd_ctl_led snd_ctl_leds[MAX_LED] = {
57cb17fe00SJaroslav Kysela {
58cb17fe00SJaroslav Kysela .name = "speaker",
59cb17fe00SJaroslav Kysela .group = (SNDRV_CTL_ELEM_ACCESS_SPK_LED >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1,
60cb17fe00SJaroslav Kysela .trigger_type = LED_AUDIO_MUTE,
61cb17fe00SJaroslav Kysela .mode = MODE_FOLLOW_MUTE,
62cb17fe00SJaroslav Kysela },
63cb17fe00SJaroslav Kysela {
64cb17fe00SJaroslav Kysela .name = "mic",
65cb17fe00SJaroslav Kysela .group = (SNDRV_CTL_ELEM_ACCESS_MIC_LED >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1,
66cb17fe00SJaroslav Kysela .trigger_type = LED_AUDIO_MICMUTE,
67cb17fe00SJaroslav Kysela .mode = MODE_FOLLOW_MUTE,
68cb17fe00SJaroslav Kysela },
69cb17fe00SJaroslav Kysela };
7022d8de62SJaroslav Kysela
71a135dfb5SJaroslav Kysela static void snd_ctl_led_sysfs_add(struct snd_card *card);
72a135dfb5SJaroslav Kysela static void snd_ctl_led_sysfs_remove(struct snd_card *card);
73a135dfb5SJaroslav Kysela
7422d8de62SJaroslav Kysela #define UPDATE_ROUTE(route, cb) \
7522d8de62SJaroslav Kysela do { \
7622d8de62SJaroslav Kysela int route2 = (cb); \
7722d8de62SJaroslav Kysela if (route2 >= 0) \
7822d8de62SJaroslav Kysela route = route < 0 ? route2 : (route | route2); \
7922d8de62SJaroslav Kysela } while (0)
8022d8de62SJaroslav Kysela
access_to_group(unsigned int access)8122d8de62SJaroslav Kysela static inline unsigned int access_to_group(unsigned int access)
8222d8de62SJaroslav Kysela {
8322d8de62SJaroslav Kysela return ((access & SNDRV_CTL_ELEM_ACCESS_LED_MASK) >>
8422d8de62SJaroslav Kysela SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1;
8522d8de62SJaroslav Kysela }
8622d8de62SJaroslav Kysela
group_to_access(unsigned int group)8722d8de62SJaroslav Kysela static inline unsigned int group_to_access(unsigned int group)
8822d8de62SJaroslav Kysela {
8922d8de62SJaroslav Kysela return (group + 1) << SNDRV_CTL_ELEM_ACCESS_LED_SHIFT;
9022d8de62SJaroslav Kysela }
9122d8de62SJaroslav Kysela
snd_ctl_led_get_by_access(unsigned int access)92cb17fe00SJaroslav Kysela static struct snd_ctl_led *snd_ctl_led_get_by_access(unsigned int access)
9322d8de62SJaroslav Kysela {
9422d8de62SJaroslav Kysela unsigned int group = access_to_group(access);
9522d8de62SJaroslav Kysela if (group >= MAX_LED)
9622d8de62SJaroslav Kysela return NULL;
97cb17fe00SJaroslav Kysela return &snd_ctl_leds[group];
9822d8de62SJaroslav Kysela }
9922d8de62SJaroslav Kysela
100543f8d78SJaroslav Kysela /*
101543f8d78SJaroslav Kysela * A note for callers:
102543f8d78SJaroslav Kysela * The two static variables info and value are protected using snd_ctl_led_mutex.
103543f8d78SJaroslav Kysela */
snd_ctl_led_get(struct snd_ctl_led_ctl * lctl)104cb17fe00SJaroslav Kysela static int snd_ctl_led_get(struct snd_ctl_led_ctl *lctl)
10522d8de62SJaroslav Kysela {
106543f8d78SJaroslav Kysela static struct snd_ctl_elem_info info;
107543f8d78SJaroslav Kysela static struct snd_ctl_elem_value value;
10822d8de62SJaroslav Kysela struct snd_kcontrol *kctl = lctl->kctl;
10922d8de62SJaroslav Kysela unsigned int i;
11022d8de62SJaroslav Kysela int result;
11122d8de62SJaroslav Kysela
11222d8de62SJaroslav Kysela memset(&info, 0, sizeof(info));
11322d8de62SJaroslav Kysela info.id = kctl->id;
11422d8de62SJaroslav Kysela info.id.index += lctl->index_offset;
11522d8de62SJaroslav Kysela info.id.numid += lctl->index_offset;
11622d8de62SJaroslav Kysela result = kctl->info(kctl, &info);
11722d8de62SJaroslav Kysela if (result < 0)
11822d8de62SJaroslav Kysela return -1;
11922d8de62SJaroslav Kysela memset(&value, 0, sizeof(value));
12022d8de62SJaroslav Kysela value.id = info.id;
12122d8de62SJaroslav Kysela result = kctl->get(kctl, &value);
12222d8de62SJaroslav Kysela if (result < 0)
12322d8de62SJaroslav Kysela return -1;
12422d8de62SJaroslav Kysela if (info.type == SNDRV_CTL_ELEM_TYPE_BOOLEAN ||
12522d8de62SJaroslav Kysela info.type == SNDRV_CTL_ELEM_TYPE_INTEGER) {
12622d8de62SJaroslav Kysela for (i = 0; i < info.count; i++)
12722d8de62SJaroslav Kysela if (value.value.integer.value[i] != info.value.integer.min)
12822d8de62SJaroslav Kysela return 1;
12922d8de62SJaroslav Kysela } else if (info.type == SNDRV_CTL_ELEM_TYPE_INTEGER64) {
13022d8de62SJaroslav Kysela for (i = 0; i < info.count; i++)
13122d8de62SJaroslav Kysela if (value.value.integer64.value[i] != info.value.integer64.min)
13222d8de62SJaroslav Kysela return 1;
13322d8de62SJaroslav Kysela }
13422d8de62SJaroslav Kysela return 0;
13522d8de62SJaroslav Kysela }
13622d8de62SJaroslav Kysela
snd_ctl_led_set_state(struct snd_card * card,unsigned int access,struct snd_kcontrol * kctl,unsigned int ioff)13722d8de62SJaroslav Kysela static void snd_ctl_led_set_state(struct snd_card *card, unsigned int access,
13822d8de62SJaroslav Kysela struct snd_kcontrol *kctl, unsigned int ioff)
13922d8de62SJaroslav Kysela {
140cb17fe00SJaroslav Kysela struct snd_ctl_led *led;
141cb17fe00SJaroslav Kysela struct snd_ctl_led_ctl *lctl;
14222d8de62SJaroslav Kysela int route;
14322d8de62SJaroslav Kysela bool found;
14422d8de62SJaroslav Kysela
145cb17fe00SJaroslav Kysela led = snd_ctl_led_get_by_access(access);
146cb17fe00SJaroslav Kysela if (!led)
14722d8de62SJaroslav Kysela return;
14822d8de62SJaroslav Kysela route = -1;
14922d8de62SJaroslav Kysela found = false;
15022d8de62SJaroslav Kysela mutex_lock(&snd_ctl_led_mutex);
15122d8de62SJaroslav Kysela /* the card may not be registered (active) at this point */
15222d8de62SJaroslav Kysela if (card && !snd_ctl_led_card_valid[card->number]) {
15322d8de62SJaroslav Kysela mutex_unlock(&snd_ctl_led_mutex);
15422d8de62SJaroslav Kysela return;
15522d8de62SJaroslav Kysela }
156cb17fe00SJaroslav Kysela list_for_each_entry(lctl, &led->controls, list) {
15722d8de62SJaroslav Kysela if (lctl->kctl == kctl && lctl->index_offset == ioff)
15822d8de62SJaroslav Kysela found = true;
15922d8de62SJaroslav Kysela UPDATE_ROUTE(route, snd_ctl_led_get(lctl));
16022d8de62SJaroslav Kysela }
16122d8de62SJaroslav Kysela if (!found && kctl && card) {
16222d8de62SJaroslav Kysela lctl = kzalloc(sizeof(*lctl), GFP_KERNEL);
16322d8de62SJaroslav Kysela if (lctl) {
16422d8de62SJaroslav Kysela lctl->card = card;
16522d8de62SJaroslav Kysela lctl->access = access;
16622d8de62SJaroslav Kysela lctl->kctl = kctl;
16722d8de62SJaroslav Kysela lctl->index_offset = ioff;
168cb17fe00SJaroslav Kysela list_add(&lctl->list, &led->controls);
16922d8de62SJaroslav Kysela UPDATE_ROUTE(route, snd_ctl_led_get(lctl));
17022d8de62SJaroslav Kysela }
17122d8de62SJaroslav Kysela }
17222d8de62SJaroslav Kysela mutex_unlock(&snd_ctl_led_mutex);
173cb17fe00SJaroslav Kysela switch (led->mode) {
174cb17fe00SJaroslav Kysela case MODE_OFF: route = 1; break;
175cb17fe00SJaroslav Kysela case MODE_ON: route = 0; break;
176cb17fe00SJaroslav Kysela case MODE_FOLLOW_ROUTE: if (route >= 0) route ^= 1; break;
177cb17fe00SJaroslav Kysela case MODE_FOLLOW_MUTE: /* noop */ break;
178cb17fe00SJaroslav Kysela }
17922d8de62SJaroslav Kysela if (route >= 0)
180cb17fe00SJaroslav Kysela ledtrig_audio_set(led->trigger_type, route ? LED_OFF : LED_ON);
18122d8de62SJaroslav Kysela }
18222d8de62SJaroslav Kysela
snd_ctl_led_find(struct snd_kcontrol * kctl,unsigned int ioff)183cb17fe00SJaroslav Kysela static struct snd_ctl_led_ctl *snd_ctl_led_find(struct snd_kcontrol *kctl, unsigned int ioff)
18422d8de62SJaroslav Kysela {
18522d8de62SJaroslav Kysela struct list_head *controls;
186cb17fe00SJaroslav Kysela struct snd_ctl_led_ctl *lctl;
18722d8de62SJaroslav Kysela unsigned int group;
18822d8de62SJaroslav Kysela
18922d8de62SJaroslav Kysela for (group = 0; group < MAX_LED; group++) {
190cb17fe00SJaroslav Kysela controls = &snd_ctl_leds[group].controls;
19122d8de62SJaroslav Kysela list_for_each_entry(lctl, controls, list)
19222d8de62SJaroslav Kysela if (lctl->kctl == kctl && lctl->index_offset == ioff)
19322d8de62SJaroslav Kysela return lctl;
19422d8de62SJaroslav Kysela }
19522d8de62SJaroslav Kysela return NULL;
19622d8de62SJaroslav Kysela }
19722d8de62SJaroslav Kysela
snd_ctl_led_remove(struct snd_kcontrol * kctl,unsigned int ioff,unsigned int access)19822d8de62SJaroslav Kysela static unsigned int snd_ctl_led_remove(struct snd_kcontrol *kctl, unsigned int ioff,
19922d8de62SJaroslav Kysela unsigned int access)
20022d8de62SJaroslav Kysela {
201cb17fe00SJaroslav Kysela struct snd_ctl_led_ctl *lctl;
20222d8de62SJaroslav Kysela unsigned int ret = 0;
20322d8de62SJaroslav Kysela
20422d8de62SJaroslav Kysela mutex_lock(&snd_ctl_led_mutex);
20522d8de62SJaroslav Kysela lctl = snd_ctl_led_find(kctl, ioff);
20622d8de62SJaroslav Kysela if (lctl && (access == 0 || access != lctl->access)) {
20722d8de62SJaroslav Kysela ret = lctl->access;
20822d8de62SJaroslav Kysela list_del(&lctl->list);
20922d8de62SJaroslav Kysela kfree(lctl);
21022d8de62SJaroslav Kysela }
21122d8de62SJaroslav Kysela mutex_unlock(&snd_ctl_led_mutex);
21222d8de62SJaroslav Kysela return ret;
21322d8de62SJaroslav Kysela }
21422d8de62SJaroslav Kysela
snd_ctl_led_notify(struct snd_card * card,unsigned int mask,struct snd_kcontrol * kctl,unsigned int ioff)21522d8de62SJaroslav Kysela static void snd_ctl_led_notify(struct snd_card *card, unsigned int mask,
21622d8de62SJaroslav Kysela struct snd_kcontrol *kctl, unsigned int ioff)
21722d8de62SJaroslav Kysela {
21822d8de62SJaroslav Kysela struct snd_kcontrol_volatile *vd;
21922d8de62SJaroslav Kysela unsigned int access, access2;
22022d8de62SJaroslav Kysela
22122d8de62SJaroslav Kysela if (mask == SNDRV_CTL_EVENT_MASK_REMOVE) {
22222d8de62SJaroslav Kysela access = snd_ctl_led_remove(kctl, ioff, 0);
22322d8de62SJaroslav Kysela if (access)
22422d8de62SJaroslav Kysela snd_ctl_led_set_state(card, access, NULL, 0);
22522d8de62SJaroslav Kysela } else if (mask & SNDRV_CTL_EVENT_MASK_INFO) {
22622d8de62SJaroslav Kysela vd = &kctl->vd[ioff];
22722d8de62SJaroslav Kysela access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK;
22822d8de62SJaroslav Kysela access2 = snd_ctl_led_remove(kctl, ioff, access);
22922d8de62SJaroslav Kysela if (access2)
23022d8de62SJaroslav Kysela snd_ctl_led_set_state(card, access2, NULL, 0);
23122d8de62SJaroslav Kysela if (access)
23222d8de62SJaroslav Kysela snd_ctl_led_set_state(card, access, kctl, ioff);
23322d8de62SJaroslav Kysela } else if ((mask & (SNDRV_CTL_EVENT_MASK_ADD |
23422d8de62SJaroslav Kysela SNDRV_CTL_EVENT_MASK_VALUE)) != 0) {
23522d8de62SJaroslav Kysela vd = &kctl->vd[ioff];
23622d8de62SJaroslav Kysela access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK;
23722d8de62SJaroslav Kysela if (access)
23822d8de62SJaroslav Kysela snd_ctl_led_set_state(card, access, kctl, ioff);
23922d8de62SJaroslav Kysela }
24022d8de62SJaroslav Kysela }
24122d8de62SJaroslav Kysela
snd_ctl_led_set_id(int card_number,struct snd_ctl_elem_id * id,unsigned int group,bool set)242a135dfb5SJaroslav Kysela static int snd_ctl_led_set_id(int card_number, struct snd_ctl_elem_id *id,
243a135dfb5SJaroslav Kysela unsigned int group, bool set)
244a135dfb5SJaroslav Kysela {
245a135dfb5SJaroslav Kysela struct snd_card *card;
246a135dfb5SJaroslav Kysela struct snd_kcontrol *kctl;
247a135dfb5SJaroslav Kysela struct snd_kcontrol_volatile *vd;
248a135dfb5SJaroslav Kysela unsigned int ioff, access, new_access;
249a135dfb5SJaroslav Kysela int err = 0;
250a135dfb5SJaroslav Kysela
251a135dfb5SJaroslav Kysela card = snd_card_ref(card_number);
252a135dfb5SJaroslav Kysela if (card) {
253a135dfb5SJaroslav Kysela down_write(&card->controls_rwsem);
254b1e055f6STakashi Iwai kctl = snd_ctl_find_id_locked(card, id);
255a135dfb5SJaroslav Kysela if (kctl) {
256a135dfb5SJaroslav Kysela ioff = snd_ctl_get_ioff(kctl, id);
257a135dfb5SJaroslav Kysela vd = &kctl->vd[ioff];
258a135dfb5SJaroslav Kysela access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK;
259a135dfb5SJaroslav Kysela if (access != 0 && access != group_to_access(group)) {
260a135dfb5SJaroslav Kysela err = -EXDEV;
261a135dfb5SJaroslav Kysela goto unlock;
262a135dfb5SJaroslav Kysela }
263a135dfb5SJaroslav Kysela new_access = vd->access & ~SNDRV_CTL_ELEM_ACCESS_LED_MASK;
264a135dfb5SJaroslav Kysela if (set)
265a135dfb5SJaroslav Kysela new_access |= group_to_access(group);
266a135dfb5SJaroslav Kysela if (new_access != vd->access) {
267a135dfb5SJaroslav Kysela vd->access = new_access;
268a135dfb5SJaroslav Kysela snd_ctl_led_notify(card, SNDRV_CTL_EVENT_MASK_INFO, kctl, ioff);
269a135dfb5SJaroslav Kysela }
270a135dfb5SJaroslav Kysela } else {
271a135dfb5SJaroslav Kysela err = -ENOENT;
272a135dfb5SJaroslav Kysela }
273a135dfb5SJaroslav Kysela unlock:
274a135dfb5SJaroslav Kysela up_write(&card->controls_rwsem);
275a135dfb5SJaroslav Kysela snd_card_unref(card);
276a135dfb5SJaroslav Kysela } else {
277a135dfb5SJaroslav Kysela err = -ENXIO;
278a135dfb5SJaroslav Kysela }
279a135dfb5SJaroslav Kysela return err;
280a135dfb5SJaroslav Kysela }
281a135dfb5SJaroslav Kysela
snd_ctl_led_refresh(void)28222d8de62SJaroslav Kysela static void snd_ctl_led_refresh(void)
28322d8de62SJaroslav Kysela {
28422d8de62SJaroslav Kysela unsigned int group;
28522d8de62SJaroslav Kysela
28622d8de62SJaroslav Kysela for (group = 0; group < MAX_LED; group++)
28722d8de62SJaroslav Kysela snd_ctl_led_set_state(NULL, group_to_access(group), NULL, 0);
28822d8de62SJaroslav Kysela }
28922d8de62SJaroslav Kysela
snd_ctl_led_ctl_destroy(struct snd_ctl_led_ctl * lctl)290a135dfb5SJaroslav Kysela static void snd_ctl_led_ctl_destroy(struct snd_ctl_led_ctl *lctl)
291a135dfb5SJaroslav Kysela {
292a135dfb5SJaroslav Kysela list_del(&lctl->list);
293a135dfb5SJaroslav Kysela kfree(lctl);
294a135dfb5SJaroslav Kysela }
295a135dfb5SJaroslav Kysela
snd_ctl_led_clean(struct snd_card * card)29622d8de62SJaroslav Kysela static void snd_ctl_led_clean(struct snd_card *card)
29722d8de62SJaroslav Kysela {
29822d8de62SJaroslav Kysela unsigned int group;
299cb17fe00SJaroslav Kysela struct snd_ctl_led *led;
300cb17fe00SJaroslav Kysela struct snd_ctl_led_ctl *lctl;
30122d8de62SJaroslav Kysela
30222d8de62SJaroslav Kysela for (group = 0; group < MAX_LED; group++) {
303cb17fe00SJaroslav Kysela led = &snd_ctl_leds[group];
30422d8de62SJaroslav Kysela repeat:
305cb17fe00SJaroslav Kysela list_for_each_entry(lctl, &led->controls, list)
30622d8de62SJaroslav Kysela if (!card || lctl->card == card) {
307a135dfb5SJaroslav Kysela snd_ctl_led_ctl_destroy(lctl);
30822d8de62SJaroslav Kysela goto repeat;
30922d8de62SJaroslav Kysela }
31022d8de62SJaroslav Kysela }
31122d8de62SJaroslav Kysela }
31222d8de62SJaroslav Kysela
snd_ctl_led_reset(int card_number,unsigned int group)313a135dfb5SJaroslav Kysela static int snd_ctl_led_reset(int card_number, unsigned int group)
314a135dfb5SJaroslav Kysela {
315a135dfb5SJaroslav Kysela struct snd_card *card;
316a135dfb5SJaroslav Kysela struct snd_ctl_led *led;
317a135dfb5SJaroslav Kysela struct snd_ctl_led_ctl *lctl;
318a135dfb5SJaroslav Kysela struct snd_kcontrol_volatile *vd;
319a135dfb5SJaroslav Kysela bool change = false;
320a135dfb5SJaroslav Kysela
321a135dfb5SJaroslav Kysela card = snd_card_ref(card_number);
322a135dfb5SJaroslav Kysela if (!card)
323a135dfb5SJaroslav Kysela return -ENXIO;
324a135dfb5SJaroslav Kysela
325a135dfb5SJaroslav Kysela mutex_lock(&snd_ctl_led_mutex);
326a135dfb5SJaroslav Kysela if (!snd_ctl_led_card_valid[card_number]) {
327a135dfb5SJaroslav Kysela mutex_unlock(&snd_ctl_led_mutex);
328a135dfb5SJaroslav Kysela snd_card_unref(card);
329a135dfb5SJaroslav Kysela return -ENXIO;
330a135dfb5SJaroslav Kysela }
331a135dfb5SJaroslav Kysela led = &snd_ctl_leds[group];
332a135dfb5SJaroslav Kysela repeat:
333a135dfb5SJaroslav Kysela list_for_each_entry(lctl, &led->controls, list)
334a135dfb5SJaroslav Kysela if (lctl->card == card) {
335a135dfb5SJaroslav Kysela vd = &lctl->kctl->vd[lctl->index_offset];
336a135dfb5SJaroslav Kysela vd->access &= ~group_to_access(group);
337a135dfb5SJaroslav Kysela snd_ctl_led_ctl_destroy(lctl);
338a135dfb5SJaroslav Kysela change = true;
339a135dfb5SJaroslav Kysela goto repeat;
340a135dfb5SJaroslav Kysela }
341a135dfb5SJaroslav Kysela mutex_unlock(&snd_ctl_led_mutex);
342a135dfb5SJaroslav Kysela if (change)
343a135dfb5SJaroslav Kysela snd_ctl_led_set_state(NULL, group_to_access(group), NULL, 0);
344a135dfb5SJaroslav Kysela snd_card_unref(card);
345a135dfb5SJaroslav Kysela return 0;
346a135dfb5SJaroslav Kysela }
347a135dfb5SJaroslav Kysela
snd_ctl_led_register(struct snd_card * card)34822d8de62SJaroslav Kysela static void snd_ctl_led_register(struct snd_card *card)
34922d8de62SJaroslav Kysela {
35022d8de62SJaroslav Kysela struct snd_kcontrol *kctl;
35122d8de62SJaroslav Kysela unsigned int ioff;
35222d8de62SJaroslav Kysela
35322d8de62SJaroslav Kysela if (snd_BUG_ON(card->number < 0 ||
35422d8de62SJaroslav Kysela card->number >= ARRAY_SIZE(snd_ctl_led_card_valid)))
35522d8de62SJaroslav Kysela return;
35622d8de62SJaroslav Kysela mutex_lock(&snd_ctl_led_mutex);
35722d8de62SJaroslav Kysela snd_ctl_led_card_valid[card->number] = true;
35822d8de62SJaroslav Kysela mutex_unlock(&snd_ctl_led_mutex);
35922d8de62SJaroslav Kysela /* the register callback is already called with held card->controls_rwsem */
36022d8de62SJaroslav Kysela list_for_each_entry(kctl, &card->controls, list)
36122d8de62SJaroslav Kysela for (ioff = 0; ioff < kctl->count; ioff++)
36222d8de62SJaroslav Kysela snd_ctl_led_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, kctl, ioff);
36322d8de62SJaroslav Kysela snd_ctl_led_refresh();
364a135dfb5SJaroslav Kysela snd_ctl_led_sysfs_add(card);
36522d8de62SJaroslav Kysela }
36622d8de62SJaroslav Kysela
snd_ctl_led_disconnect(struct snd_card * card)36722d8de62SJaroslav Kysela static void snd_ctl_led_disconnect(struct snd_card *card)
36822d8de62SJaroslav Kysela {
369a135dfb5SJaroslav Kysela snd_ctl_led_sysfs_remove(card);
37022d8de62SJaroslav Kysela mutex_lock(&snd_ctl_led_mutex);
37122d8de62SJaroslav Kysela snd_ctl_led_card_valid[card->number] = false;
37222d8de62SJaroslav Kysela snd_ctl_led_clean(card);
37322d8de62SJaroslav Kysela mutex_unlock(&snd_ctl_led_mutex);
37422d8de62SJaroslav Kysela snd_ctl_led_refresh();
37522d8de62SJaroslav Kysela }
37622d8de62SJaroslav Kysela
snd_ctl_led_card_release(struct device * dev)3773ae72f6aSDongliang Mu static void snd_ctl_led_card_release(struct device *dev)
3783ae72f6aSDongliang Mu {
3793ae72f6aSDongliang Mu struct snd_ctl_led_card *led_card = to_led_card_dev(dev);
3803ae72f6aSDongliang Mu
3813ae72f6aSDongliang Mu kfree(led_card);
3823ae72f6aSDongliang Mu }
3833ae72f6aSDongliang Mu
snd_ctl_led_release(struct device * dev)3843ae72f6aSDongliang Mu static void snd_ctl_led_release(struct device *dev)
3853ae72f6aSDongliang Mu {
3863ae72f6aSDongliang Mu }
3873ae72f6aSDongliang Mu
snd_ctl_led_dev_release(struct device * dev)3883ae72f6aSDongliang Mu static void snd_ctl_led_dev_release(struct device *dev)
3893ae72f6aSDongliang Mu {
3903ae72f6aSDongliang Mu }
3913ae72f6aSDongliang Mu
39222d8de62SJaroslav Kysela /*
393cb17fe00SJaroslav Kysela * sysfs
394cb17fe00SJaroslav Kysela */
395cb17fe00SJaroslav Kysela
mode_show(struct device * dev,struct device_attribute * attr,char * buf)39608e767cdSYueHaibing static ssize_t mode_show(struct device *dev,
397cb17fe00SJaroslav Kysela struct device_attribute *attr, char *buf)
398cb17fe00SJaroslav Kysela {
399cb17fe00SJaroslav Kysela struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev);
400e381a14cSJaroslav Kysela const char *str = NULL;
401cb17fe00SJaroslav Kysela
402cb17fe00SJaroslav Kysela switch (led->mode) {
403cb17fe00SJaroslav Kysela case MODE_FOLLOW_MUTE: str = "follow-mute"; break;
404cb17fe00SJaroslav Kysela case MODE_FOLLOW_ROUTE: str = "follow-route"; break;
405cb17fe00SJaroslav Kysela case MODE_ON: str = "on"; break;
406cb17fe00SJaroslav Kysela case MODE_OFF: str = "off"; break;
407cb17fe00SJaroslav Kysela }
408ade79563STakashi Iwai return sysfs_emit(buf, "%s\n", str);
409cb17fe00SJaroslav Kysela }
410cb17fe00SJaroslav Kysela
mode_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)41108e767cdSYueHaibing static ssize_t mode_store(struct device *dev,
41208e767cdSYueHaibing struct device_attribute *attr,
413cb17fe00SJaroslav Kysela const char *buf, size_t count)
414cb17fe00SJaroslav Kysela {
415cb17fe00SJaroslav Kysela struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev);
416cb17fe00SJaroslav Kysela char _buf[16];
41753cc2643SDan Carpenter size_t l = min(count, sizeof(_buf) - 1);
418cb17fe00SJaroslav Kysela enum snd_ctl_led_mode mode;
419cb17fe00SJaroslav Kysela
420cb17fe00SJaroslav Kysela memcpy(_buf, buf, l);
421cb17fe00SJaroslav Kysela _buf[l] = '\0';
422cb17fe00SJaroslav Kysela if (strstr(_buf, "mute"))
423cb17fe00SJaroslav Kysela mode = MODE_FOLLOW_MUTE;
424cb17fe00SJaroslav Kysela else if (strstr(_buf, "route"))
425cb17fe00SJaroslav Kysela mode = MODE_FOLLOW_ROUTE;
426cb17fe00SJaroslav Kysela else if (strncmp(_buf, "off", 3) == 0 || strncmp(_buf, "0", 1) == 0)
427cb17fe00SJaroslav Kysela mode = MODE_OFF;
428cb17fe00SJaroslav Kysela else if (strncmp(_buf, "on", 2) == 0 || strncmp(_buf, "1", 1) == 0)
429cb17fe00SJaroslav Kysela mode = MODE_ON;
430cb17fe00SJaroslav Kysela else
431cb17fe00SJaroslav Kysela return count;
432cb17fe00SJaroslav Kysela
433cb17fe00SJaroslav Kysela mutex_lock(&snd_ctl_led_mutex);
434cb17fe00SJaroslav Kysela led->mode = mode;
435cb17fe00SJaroslav Kysela mutex_unlock(&snd_ctl_led_mutex);
436cb17fe00SJaroslav Kysela
437cb17fe00SJaroslav Kysela snd_ctl_led_set_state(NULL, group_to_access(led->group), NULL, 0);
438cb17fe00SJaroslav Kysela return count;
439cb17fe00SJaroslav Kysela }
440cb17fe00SJaroslav Kysela
brightness_show(struct device * dev,struct device_attribute * attr,char * buf)44108e767cdSYueHaibing static ssize_t brightness_show(struct device *dev,
442cb17fe00SJaroslav Kysela struct device_attribute *attr, char *buf)
443cb17fe00SJaroslav Kysela {
444cb17fe00SJaroslav Kysela struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev);
445cb17fe00SJaroslav Kysela
446ade79563STakashi Iwai return sysfs_emit(buf, "%u\n", ledtrig_audio_get(led->trigger_type));
447cb17fe00SJaroslav Kysela }
448cb17fe00SJaroslav Kysela
44908e767cdSYueHaibing static DEVICE_ATTR_RW(mode);
45008e767cdSYueHaibing static DEVICE_ATTR_RO(brightness);
451cb17fe00SJaroslav Kysela
452cb17fe00SJaroslav Kysela static struct attribute *snd_ctl_led_dev_attrs[] = {
453cb17fe00SJaroslav Kysela &dev_attr_mode.attr,
454cb17fe00SJaroslav Kysela &dev_attr_brightness.attr,
455cb17fe00SJaroslav Kysela NULL,
456cb17fe00SJaroslav Kysela };
457cb17fe00SJaroslav Kysela
458cb17fe00SJaroslav Kysela static const struct attribute_group snd_ctl_led_dev_attr_group = {
459cb17fe00SJaroslav Kysela .attrs = snd_ctl_led_dev_attrs,
460cb17fe00SJaroslav Kysela };
461cb17fe00SJaroslav Kysela
462cb17fe00SJaroslav Kysela static const struct attribute_group *snd_ctl_led_dev_attr_groups[] = {
463cb17fe00SJaroslav Kysela &snd_ctl_led_dev_attr_group,
464cb17fe00SJaroslav Kysela NULL,
465cb17fe00SJaroslav Kysela };
466cb17fe00SJaroslav Kysela
find_eos(char * s)467a135dfb5SJaroslav Kysela static char *find_eos(char *s)
468a135dfb5SJaroslav Kysela {
469a135dfb5SJaroslav Kysela while (*s && *s != ',')
470a135dfb5SJaroslav Kysela s++;
471a135dfb5SJaroslav Kysela if (*s)
472a135dfb5SJaroslav Kysela s++;
473a135dfb5SJaroslav Kysela return s;
474a135dfb5SJaroslav Kysela }
475a135dfb5SJaroslav Kysela
parse_uint(char * s,unsigned int * val)476a135dfb5SJaroslav Kysela static char *parse_uint(char *s, unsigned int *val)
477a135dfb5SJaroslav Kysela {
478a135dfb5SJaroslav Kysela unsigned long long res;
479a135dfb5SJaroslav Kysela if (kstrtoull(s, 10, &res))
480a135dfb5SJaroslav Kysela res = 0;
481a135dfb5SJaroslav Kysela *val = res;
482a135dfb5SJaroslav Kysela return find_eos(s);
483a135dfb5SJaroslav Kysela }
484a135dfb5SJaroslav Kysela
parse_string(char * s,char * val,size_t val_size)485a135dfb5SJaroslav Kysela static char *parse_string(char *s, char *val, size_t val_size)
486a135dfb5SJaroslav Kysela {
487a135dfb5SJaroslav Kysela if (*s == '"' || *s == '\'') {
488a135dfb5SJaroslav Kysela char c = *s;
489a135dfb5SJaroslav Kysela s++;
490a135dfb5SJaroslav Kysela while (*s && *s != c) {
491a135dfb5SJaroslav Kysela if (val_size > 1) {
492a135dfb5SJaroslav Kysela *val++ = *s;
493a135dfb5SJaroslav Kysela val_size--;
494a135dfb5SJaroslav Kysela }
495a135dfb5SJaroslav Kysela s++;
496a135dfb5SJaroslav Kysela }
497a135dfb5SJaroslav Kysela } else {
498a135dfb5SJaroslav Kysela while (*s && *s != ',') {
499a135dfb5SJaroslav Kysela if (val_size > 1) {
500a135dfb5SJaroslav Kysela *val++ = *s;
501a135dfb5SJaroslav Kysela val_size--;
502a135dfb5SJaroslav Kysela }
503a135dfb5SJaroslav Kysela s++;
504a135dfb5SJaroslav Kysela }
505a135dfb5SJaroslav Kysela }
506a135dfb5SJaroslav Kysela *val = '\0';
507a135dfb5SJaroslav Kysela if (*s)
508a135dfb5SJaroslav Kysela s++;
509a135dfb5SJaroslav Kysela return s;
510a135dfb5SJaroslav Kysela }
511a135dfb5SJaroslav Kysela
parse_iface(char * s,snd_ctl_elem_iface_t * val)5127c72665cSTakashi Iwai static char *parse_iface(char *s, snd_ctl_elem_iface_t *val)
513a135dfb5SJaroslav Kysela {
514a135dfb5SJaroslav Kysela if (!strncasecmp(s, "card", 4))
515a135dfb5SJaroslav Kysela *val = SNDRV_CTL_ELEM_IFACE_CARD;
516a135dfb5SJaroslav Kysela else if (!strncasecmp(s, "mixer", 5))
517a135dfb5SJaroslav Kysela *val = SNDRV_CTL_ELEM_IFACE_MIXER;
518a135dfb5SJaroslav Kysela return find_eos(s);
519a135dfb5SJaroslav Kysela }
520a135dfb5SJaroslav Kysela
521a135dfb5SJaroslav Kysela /*
522a135dfb5SJaroslav Kysela * These types of input strings are accepted:
523a135dfb5SJaroslav Kysela *
524a135dfb5SJaroslav Kysela * unsigned integer - numid (equivaled to numid=UINT)
525a135dfb5SJaroslav Kysela * string - basic mixer name (equivalent to iface=MIXER,name=STR)
526a135dfb5SJaroslav Kysela * numid=UINT
527a135dfb5SJaroslav Kysela * [iface=MIXER,][device=UINT,][subdevice=UINT,]name=STR[,index=UINT]
528a135dfb5SJaroslav Kysela */
set_led_id(struct snd_ctl_led_card * led_card,const char * buf,size_t count,bool attach)529a135dfb5SJaroslav Kysela static ssize_t set_led_id(struct snd_ctl_led_card *led_card, const char *buf, size_t count,
530a135dfb5SJaroslav Kysela bool attach)
531a135dfb5SJaroslav Kysela {
53262327ebbSJaroslav Kysela char buf2[256], *s, *os;
533a135dfb5SJaroslav Kysela struct snd_ctl_elem_id id;
534a135dfb5SJaroslav Kysela int err;
535a135dfb5SJaroslav Kysela
53670051cffSJaroslav Kysela if (strscpy(buf2, buf, sizeof(buf2)) < 0)
53770051cffSJaroslav Kysela return -E2BIG;
538a135dfb5SJaroslav Kysela memset(&id, 0, sizeof(id));
539a135dfb5SJaroslav Kysela id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
540a135dfb5SJaroslav Kysela s = buf2;
541a135dfb5SJaroslav Kysela while (*s) {
54262327ebbSJaroslav Kysela os = s;
543a135dfb5SJaroslav Kysela if (!strncasecmp(s, "numid=", 6)) {
544a135dfb5SJaroslav Kysela s = parse_uint(s + 6, &id.numid);
545a135dfb5SJaroslav Kysela } else if (!strncasecmp(s, "iface=", 6)) {
546a135dfb5SJaroslav Kysela s = parse_iface(s + 6, &id.iface);
547a135dfb5SJaroslav Kysela } else if (!strncasecmp(s, "device=", 7)) {
548a135dfb5SJaroslav Kysela s = parse_uint(s + 7, &id.device);
549a135dfb5SJaroslav Kysela } else if (!strncasecmp(s, "subdevice=", 10)) {
550a135dfb5SJaroslav Kysela s = parse_uint(s + 10, &id.subdevice);
551a135dfb5SJaroslav Kysela } else if (!strncasecmp(s, "name=", 5)) {
552a135dfb5SJaroslav Kysela s = parse_string(s + 5, id.name, sizeof(id.name));
553a135dfb5SJaroslav Kysela } else if (!strncasecmp(s, "index=", 6)) {
554a135dfb5SJaroslav Kysela s = parse_uint(s + 6, &id.index);
555a135dfb5SJaroslav Kysela } else if (s == buf2) {
556a135dfb5SJaroslav Kysela while (*s) {
557a135dfb5SJaroslav Kysela if (*s < '0' || *s > '9')
558a135dfb5SJaroslav Kysela break;
559a135dfb5SJaroslav Kysela s++;
560a135dfb5SJaroslav Kysela }
561a135dfb5SJaroslav Kysela if (*s == '\0')
562a135dfb5SJaroslav Kysela parse_uint(buf2, &id.numid);
563a135dfb5SJaroslav Kysela else {
564a135dfb5SJaroslav Kysela for (; *s >= ' '; s++);
565a135dfb5SJaroslav Kysela *s = '\0';
566360a5812SPierre-Louis Bossart strscpy(id.name, buf2, sizeof(id.name));
567a135dfb5SJaroslav Kysela }
568a135dfb5SJaroslav Kysela break;
569a135dfb5SJaroslav Kysela }
570a135dfb5SJaroslav Kysela if (*s == ',')
571a135dfb5SJaroslav Kysela s++;
57262327ebbSJaroslav Kysela if (s == os)
57362327ebbSJaroslav Kysela break;
574a135dfb5SJaroslav Kysela }
575a135dfb5SJaroslav Kysela
576a135dfb5SJaroslav Kysela err = snd_ctl_led_set_id(led_card->number, &id, led_card->led->group, attach);
577a135dfb5SJaroslav Kysela if (err < 0)
578a135dfb5SJaroslav Kysela return err;
579a135dfb5SJaroslav Kysela
580a135dfb5SJaroslav Kysela return count;
581a135dfb5SJaroslav Kysela }
582a135dfb5SJaroslav Kysela
attach_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)58308e767cdSYueHaibing static ssize_t attach_store(struct device *dev,
58408e767cdSYueHaibing struct device_attribute *attr,
585a135dfb5SJaroslav Kysela const char *buf, size_t count)
586a135dfb5SJaroslav Kysela {
587a135dfb5SJaroslav Kysela struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev);
588a135dfb5SJaroslav Kysela return set_led_id(led_card, buf, count, true);
589a135dfb5SJaroslav Kysela }
590a135dfb5SJaroslav Kysela
detach_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)59108e767cdSYueHaibing static ssize_t detach_store(struct device *dev,
59208e767cdSYueHaibing struct device_attribute *attr,
593a135dfb5SJaroslav Kysela const char *buf, size_t count)
594a135dfb5SJaroslav Kysela {
595a135dfb5SJaroslav Kysela struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev);
596a135dfb5SJaroslav Kysela return set_led_id(led_card, buf, count, false);
597a135dfb5SJaroslav Kysela }
598a135dfb5SJaroslav Kysela
reset_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)59908e767cdSYueHaibing static ssize_t reset_store(struct device *dev,
60008e767cdSYueHaibing struct device_attribute *attr,
601a135dfb5SJaroslav Kysela const char *buf, size_t count)
602a135dfb5SJaroslav Kysela {
603a135dfb5SJaroslav Kysela struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev);
604a135dfb5SJaroslav Kysela int err;
605a135dfb5SJaroslav Kysela
606a135dfb5SJaroslav Kysela if (count > 0 && buf[0] == '1') {
607a135dfb5SJaroslav Kysela err = snd_ctl_led_reset(led_card->number, led_card->led->group);
608a135dfb5SJaroslav Kysela if (err < 0)
609a135dfb5SJaroslav Kysela return err;
610a135dfb5SJaroslav Kysela }
611a135dfb5SJaroslav Kysela return count;
612a135dfb5SJaroslav Kysela }
613a135dfb5SJaroslav Kysela
list_show(struct device * dev,struct device_attribute * attr,char * buf)61408e767cdSYueHaibing static ssize_t list_show(struct device *dev,
615a135dfb5SJaroslav Kysela struct device_attribute *attr, char *buf)
616a135dfb5SJaroslav Kysela {
617a135dfb5SJaroslav Kysela struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev);
618a135dfb5SJaroslav Kysela struct snd_card *card;
619a135dfb5SJaroslav Kysela struct snd_ctl_led_ctl *lctl;
620ade79563STakashi Iwai size_t l = 0;
621a135dfb5SJaroslav Kysela
622a135dfb5SJaroslav Kysela card = snd_card_ref(led_card->number);
623a135dfb5SJaroslav Kysela if (!card)
624a135dfb5SJaroslav Kysela return -ENXIO;
625a135dfb5SJaroslav Kysela down_read(&card->controls_rwsem);
626a135dfb5SJaroslav Kysela mutex_lock(&snd_ctl_led_mutex);
627a135dfb5SJaroslav Kysela if (snd_ctl_led_card_valid[led_card->number]) {
628ade79563STakashi Iwai list_for_each_entry(lctl, &led_card->led->controls, list) {
629ade79563STakashi Iwai if (lctl->card != card)
630ade79563STakashi Iwai continue;
631ade79563STakashi Iwai if (l)
632ade79563STakashi Iwai l += sysfs_emit_at(buf, l, " ");
633ade79563STakashi Iwai l += sysfs_emit_at(buf, l, "%u",
634ade79563STakashi Iwai lctl->kctl->id.numid + lctl->index_offset);
635a135dfb5SJaroslav Kysela }
636a135dfb5SJaroslav Kysela }
637a135dfb5SJaroslav Kysela mutex_unlock(&snd_ctl_led_mutex);
638a135dfb5SJaroslav Kysela up_read(&card->controls_rwsem);
639a135dfb5SJaroslav Kysela snd_card_unref(card);
640ade79563STakashi Iwai return l;
641a135dfb5SJaroslav Kysela }
642a135dfb5SJaroslav Kysela
64308e767cdSYueHaibing static DEVICE_ATTR_WO(attach);
64408e767cdSYueHaibing static DEVICE_ATTR_WO(detach);
64508e767cdSYueHaibing static DEVICE_ATTR_WO(reset);
64608e767cdSYueHaibing static DEVICE_ATTR_RO(list);
647a135dfb5SJaroslav Kysela
648a135dfb5SJaroslav Kysela static struct attribute *snd_ctl_led_card_attrs[] = {
649a135dfb5SJaroslav Kysela &dev_attr_attach.attr,
650a135dfb5SJaroslav Kysela &dev_attr_detach.attr,
651a135dfb5SJaroslav Kysela &dev_attr_reset.attr,
652a135dfb5SJaroslav Kysela &dev_attr_list.attr,
653a135dfb5SJaroslav Kysela NULL,
654a135dfb5SJaroslav Kysela };
655a135dfb5SJaroslav Kysela
656a135dfb5SJaroslav Kysela static const struct attribute_group snd_ctl_led_card_attr_group = {
657a135dfb5SJaroslav Kysela .attrs = snd_ctl_led_card_attrs,
658a135dfb5SJaroslav Kysela };
659a135dfb5SJaroslav Kysela
660a135dfb5SJaroslav Kysela static const struct attribute_group *snd_ctl_led_card_attr_groups[] = {
661a135dfb5SJaroslav Kysela &snd_ctl_led_card_attr_group,
662a135dfb5SJaroslav Kysela NULL,
663a135dfb5SJaroslav Kysela };
664a135dfb5SJaroslav Kysela
665cb17fe00SJaroslav Kysela static struct device snd_ctl_led_dev;
666cb17fe00SJaroslav Kysela
snd_ctl_led_sysfs_add(struct snd_card * card)667a135dfb5SJaroslav Kysela static void snd_ctl_led_sysfs_add(struct snd_card *card)
668a135dfb5SJaroslav Kysela {
669a135dfb5SJaroslav Kysela unsigned int group;
670a135dfb5SJaroslav Kysela struct snd_ctl_led_card *led_card;
671a135dfb5SJaroslav Kysela struct snd_ctl_led *led;
672a135dfb5SJaroslav Kysela char link_name[32];
673a135dfb5SJaroslav Kysela
674a135dfb5SJaroslav Kysela for (group = 0; group < MAX_LED; group++) {
675a135dfb5SJaroslav Kysela led = &snd_ctl_leds[group];
676a135dfb5SJaroslav Kysela led_card = kzalloc(sizeof(*led_card), GFP_KERNEL);
677a135dfb5SJaroslav Kysela if (!led_card)
678a135dfb5SJaroslav Kysela goto cerr2;
679a135dfb5SJaroslav Kysela led_card->number = card->number;
680a135dfb5SJaroslav Kysela led_card->led = led;
681a135dfb5SJaroslav Kysela device_initialize(&led_card->dev);
6823ae72f6aSDongliang Mu led_card->dev.release = snd_ctl_led_card_release;
683a135dfb5SJaroslav Kysela if (dev_set_name(&led_card->dev, "card%d", card->number) < 0)
684a135dfb5SJaroslav Kysela goto cerr;
685a135dfb5SJaroslav Kysela led_card->dev.parent = &led->dev;
686a135dfb5SJaroslav Kysela led_card->dev.groups = snd_ctl_led_card_attr_groups;
687a135dfb5SJaroslav Kysela if (device_add(&led_card->dev))
688a135dfb5SJaroslav Kysela goto cerr;
689a135dfb5SJaroslav Kysela led->cards[card->number] = led_card;
690a135dfb5SJaroslav Kysela snprintf(link_name, sizeof(link_name), "led-%s", led->name);
691*d5a1ca7bSTakashi Iwai if (sysfs_create_link(&card->ctl_dev->kobj, &led_card->dev.kobj,
692*d5a1ca7bSTakashi Iwai link_name))
693*d5a1ca7bSTakashi Iwai dev_err(card->dev,
694*d5a1ca7bSTakashi Iwai "%s: can't create symlink to controlC%i device\n",
695*d5a1ca7bSTakashi Iwai __func__, card->number);
696*d5a1ca7bSTakashi Iwai if (sysfs_create_link(&led_card->dev.kobj, &card->card_dev.kobj,
697*d5a1ca7bSTakashi Iwai "card"))
698*d5a1ca7bSTakashi Iwai dev_err(card->dev,
699*d5a1ca7bSTakashi Iwai "%s: can't create symlink to card%i\n",
700*d5a1ca7bSTakashi Iwai __func__, card->number);
701a135dfb5SJaroslav Kysela
702a135dfb5SJaroslav Kysela continue;
703a135dfb5SJaroslav Kysela cerr:
704a135dfb5SJaroslav Kysela put_device(&led_card->dev);
705a135dfb5SJaroslav Kysela cerr2:
706a135dfb5SJaroslav Kysela printk(KERN_ERR "snd_ctl_led: unable to add card%d", card->number);
707a135dfb5SJaroslav Kysela }
708a135dfb5SJaroslav Kysela }
709a135dfb5SJaroslav Kysela
snd_ctl_led_sysfs_remove(struct snd_card * card)710a135dfb5SJaroslav Kysela static void snd_ctl_led_sysfs_remove(struct snd_card *card)
711a135dfb5SJaroslav Kysela {
712a135dfb5SJaroslav Kysela unsigned int group;
713a135dfb5SJaroslav Kysela struct snd_ctl_led_card *led_card;
714a135dfb5SJaroslav Kysela struct snd_ctl_led *led;
715a135dfb5SJaroslav Kysela char link_name[32];
716a135dfb5SJaroslav Kysela
717a135dfb5SJaroslav Kysela for (group = 0; group < MAX_LED; group++) {
718a135dfb5SJaroslav Kysela led = &snd_ctl_leds[group];
719a135dfb5SJaroslav Kysela led_card = led->cards[card->number];
720a135dfb5SJaroslav Kysela if (!led_card)
721a135dfb5SJaroslav Kysela continue;
722a135dfb5SJaroslav Kysela snprintf(link_name, sizeof(link_name), "led-%s", led->name);
7236a66b01dSTakashi Iwai sysfs_remove_link(&card->ctl_dev->kobj, link_name);
724a135dfb5SJaroslav Kysela sysfs_remove_link(&led_card->dev.kobj, "card");
7253ae72f6aSDongliang Mu device_unregister(&led_card->dev);
726a135dfb5SJaroslav Kysela led->cards[card->number] = NULL;
727a135dfb5SJaroslav Kysela }
728a135dfb5SJaroslav Kysela }
729a135dfb5SJaroslav Kysela
730cb17fe00SJaroslav Kysela /*
73122d8de62SJaroslav Kysela * Control layer registration
73222d8de62SJaroslav Kysela */
73322d8de62SJaroslav Kysela static struct snd_ctl_layer_ops snd_ctl_led_lops = {
73422d8de62SJaroslav Kysela .module_name = SND_CTL_LAYER_MODULE_LED,
73522d8de62SJaroslav Kysela .lregister = snd_ctl_led_register,
73622d8de62SJaroslav Kysela .ldisconnect = snd_ctl_led_disconnect,
73722d8de62SJaroslav Kysela .lnotify = snd_ctl_led_notify,
73822d8de62SJaroslav Kysela };
73922d8de62SJaroslav Kysela
snd_ctl_led_init(void)74022d8de62SJaroslav Kysela static int __init snd_ctl_led_init(void)
74122d8de62SJaroslav Kysela {
742cb17fe00SJaroslav Kysela struct snd_ctl_led *led;
74322d8de62SJaroslav Kysela unsigned int group;
74422d8de62SJaroslav Kysela
745cb17fe00SJaroslav Kysela device_initialize(&snd_ctl_led_dev);
7468d0cf150SIvan Orlov snd_ctl_led_dev.class = &sound_class;
7473ae72f6aSDongliang Mu snd_ctl_led_dev.release = snd_ctl_led_dev_release;
748cb17fe00SJaroslav Kysela dev_set_name(&snd_ctl_led_dev, "ctl-led");
749cb17fe00SJaroslav Kysela if (device_add(&snd_ctl_led_dev)) {
750cb17fe00SJaroslav Kysela put_device(&snd_ctl_led_dev);
751cb17fe00SJaroslav Kysela return -ENOMEM;
752cb17fe00SJaroslav Kysela }
753cb17fe00SJaroslav Kysela for (group = 0; group < MAX_LED; group++) {
754cb17fe00SJaroslav Kysela led = &snd_ctl_leds[group];
755cb17fe00SJaroslav Kysela INIT_LIST_HEAD(&led->controls);
756cb17fe00SJaroslav Kysela device_initialize(&led->dev);
757cb17fe00SJaroslav Kysela led->dev.parent = &snd_ctl_led_dev;
7583ae72f6aSDongliang Mu led->dev.release = snd_ctl_led_release;
759cb17fe00SJaroslav Kysela led->dev.groups = snd_ctl_led_dev_attr_groups;
760cb17fe00SJaroslav Kysela dev_set_name(&led->dev, led->name);
761cb17fe00SJaroslav Kysela if (device_add(&led->dev)) {
762cb17fe00SJaroslav Kysela put_device(&led->dev);
763cb17fe00SJaroslav Kysela for (; group > 0; group--) {
76457b138ddSDan Carpenter led = &snd_ctl_leds[group - 1];
7653ae72f6aSDongliang Mu device_unregister(&led->dev);
766cb17fe00SJaroslav Kysela }
7673ae72f6aSDongliang Mu device_unregister(&snd_ctl_led_dev);
768cb17fe00SJaroslav Kysela return -ENOMEM;
769cb17fe00SJaroslav Kysela }
770cb17fe00SJaroslav Kysela }
77122d8de62SJaroslav Kysela snd_ctl_register_layer(&snd_ctl_led_lops);
77222d8de62SJaroslav Kysela return 0;
77322d8de62SJaroslav Kysela }
77422d8de62SJaroslav Kysela
snd_ctl_led_exit(void)77522d8de62SJaroslav Kysela static void __exit snd_ctl_led_exit(void)
77622d8de62SJaroslav Kysela {
777cb17fe00SJaroslav Kysela struct snd_ctl_led *led;
778a135dfb5SJaroslav Kysela struct snd_card *card;
779a135dfb5SJaroslav Kysela unsigned int group, card_number;
780cb17fe00SJaroslav Kysela
781a135dfb5SJaroslav Kysela snd_ctl_disconnect_layer(&snd_ctl_led_lops);
782a135dfb5SJaroslav Kysela for (card_number = 0; card_number < SNDRV_CARDS; card_number++) {
783a135dfb5SJaroslav Kysela if (!snd_ctl_led_card_valid[card_number])
784a135dfb5SJaroslav Kysela continue;
785a135dfb5SJaroslav Kysela card = snd_card_ref(card_number);
786a135dfb5SJaroslav Kysela if (card) {
787a135dfb5SJaroslav Kysela snd_ctl_led_sysfs_remove(card);
788a135dfb5SJaroslav Kysela snd_card_unref(card);
789a135dfb5SJaroslav Kysela }
790a135dfb5SJaroslav Kysela }
791cb17fe00SJaroslav Kysela for (group = 0; group < MAX_LED; group++) {
792cb17fe00SJaroslav Kysela led = &snd_ctl_leds[group];
7933ae72f6aSDongliang Mu device_unregister(&led->dev);
794cb17fe00SJaroslav Kysela }
7953ae72f6aSDongliang Mu device_unregister(&snd_ctl_led_dev);
79622d8de62SJaroslav Kysela snd_ctl_led_clean(NULL);
79722d8de62SJaroslav Kysela }
79822d8de62SJaroslav Kysela
79922d8de62SJaroslav Kysela module_init(snd_ctl_led_init)
80022d8de62SJaroslav Kysela module_exit(snd_ctl_led_exit)
801