1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * LED state routines for driver control interface
4 * Copyright (c) 2021 by Jaroslav Kysela <perex@perex.cz>
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>
13 MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
14 MODULE_DESCRIPTION("ALSA control interface to LED trigger code.");
15 MODULE_LICENSE("GPL");
17 #define MAX_LED (((SNDRV_CTL_ELEM_ACCESS_MIC_LED - SNDRV_CTL_ELEM_ACCESS_SPK_LED) \
18 >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) + 1)
21 struct list_head list;
22 struct snd_card *card;
24 struct snd_kcontrol *kctl;
25 unsigned int index_offset;
28 static DEFINE_MUTEX(snd_ctl_led_mutex);
29 static struct list_head snd_ctl_led_controls[MAX_LED];
30 static bool snd_ctl_led_card_valid[SNDRV_CARDS];
32 #define UPDATE_ROUTE(route, cb) \
36 route = route < 0 ? route2 : (route | route2); \
39 static inline unsigned int access_to_group(unsigned int access)
41 return ((access & SNDRV_CTL_ELEM_ACCESS_LED_MASK) >>
42 SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1;
45 static inline unsigned int group_to_access(unsigned int group)
47 return (group + 1) << SNDRV_CTL_ELEM_ACCESS_LED_SHIFT;
50 static struct list_head *snd_ctl_led_controls_by_access(unsigned int access)
52 unsigned int group = access_to_group(access);
55 return &snd_ctl_led_controls[group];
58 static int snd_ctl_led_get(struct snd_ctl_led *lctl)
60 struct snd_kcontrol *kctl = lctl->kctl;
61 struct snd_ctl_elem_info info;
62 struct snd_ctl_elem_value value;
66 memset(&info, 0, sizeof(info));
68 info.id.index += lctl->index_offset;
69 info.id.numid += lctl->index_offset;
70 result = kctl->info(kctl, &info);
73 memset(&value, 0, sizeof(value));
75 result = kctl->get(kctl, &value);
78 if (info.type == SNDRV_CTL_ELEM_TYPE_BOOLEAN ||
79 info.type == SNDRV_CTL_ELEM_TYPE_INTEGER) {
80 for (i = 0; i < info.count; i++)
81 if (value.value.integer.value[i] != info.value.integer.min)
83 } else if (info.type == SNDRV_CTL_ELEM_TYPE_INTEGER64) {
84 for (i = 0; i < info.count; i++)
85 if (value.value.integer64.value[i] != info.value.integer64.min)
91 static void snd_ctl_led_set_state(struct snd_card *card, unsigned int access,
92 struct snd_kcontrol *kctl, unsigned int ioff)
94 struct list_head *controls;
95 struct snd_ctl_led *lctl;
96 enum led_audio led_trigger_type;
100 controls = snd_ctl_led_controls_by_access(access);
103 if (access == SNDRV_CTL_ELEM_ACCESS_SPK_LED) {
104 led_trigger_type = LED_AUDIO_MUTE;
105 } else if (access == SNDRV_CTL_ELEM_ACCESS_MIC_LED) {
106 led_trigger_type = LED_AUDIO_MICMUTE;
112 mutex_lock(&snd_ctl_led_mutex);
113 /* the card may not be registered (active) at this point */
114 if (card && !snd_ctl_led_card_valid[card->number]) {
115 mutex_unlock(&snd_ctl_led_mutex);
118 list_for_each_entry(lctl, controls, list) {
119 if (lctl->kctl == kctl && lctl->index_offset == ioff)
121 UPDATE_ROUTE(route, snd_ctl_led_get(lctl));
123 if (!found && kctl && card) {
124 lctl = kzalloc(sizeof(*lctl), GFP_KERNEL);
127 lctl->access = access;
129 lctl->index_offset = ioff;
130 list_add(&lctl->list, controls);
131 UPDATE_ROUTE(route, snd_ctl_led_get(lctl));
134 mutex_unlock(&snd_ctl_led_mutex);
136 ledtrig_audio_set(led_trigger_type, route ? LED_OFF : LED_ON);
139 static struct snd_ctl_led *snd_ctl_led_find(struct snd_kcontrol *kctl, unsigned int ioff)
141 struct list_head *controls;
142 struct snd_ctl_led *lctl;
145 for (group = 0; group < MAX_LED; group++) {
146 controls = &snd_ctl_led_controls[group];
147 list_for_each_entry(lctl, controls, list)
148 if (lctl->kctl == kctl && lctl->index_offset == ioff)
154 static unsigned int snd_ctl_led_remove(struct snd_kcontrol *kctl, unsigned int ioff,
157 struct snd_ctl_led *lctl;
158 unsigned int ret = 0;
160 mutex_lock(&snd_ctl_led_mutex);
161 lctl = snd_ctl_led_find(kctl, ioff);
162 if (lctl && (access == 0 || access != lctl->access)) {
164 list_del(&lctl->list);
167 mutex_unlock(&snd_ctl_led_mutex);
171 static void snd_ctl_led_notify(struct snd_card *card, unsigned int mask,
172 struct snd_kcontrol *kctl, unsigned int ioff)
174 struct snd_kcontrol_volatile *vd;
175 unsigned int access, access2;
177 if (mask == SNDRV_CTL_EVENT_MASK_REMOVE) {
178 access = snd_ctl_led_remove(kctl, ioff, 0);
180 snd_ctl_led_set_state(card, access, NULL, 0);
181 } else if (mask & SNDRV_CTL_EVENT_MASK_INFO) {
182 vd = &kctl->vd[ioff];
183 access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK;
184 access2 = snd_ctl_led_remove(kctl, ioff, access);
186 snd_ctl_led_set_state(card, access2, NULL, 0);
188 snd_ctl_led_set_state(card, access, kctl, ioff);
189 } else if ((mask & (SNDRV_CTL_EVENT_MASK_ADD |
190 SNDRV_CTL_EVENT_MASK_VALUE)) != 0) {
191 vd = &kctl->vd[ioff];
192 access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK;
194 snd_ctl_led_set_state(card, access, kctl, ioff);
198 static void snd_ctl_led_refresh(void)
202 for (group = 0; group < MAX_LED; group++)
203 snd_ctl_led_set_state(NULL, group_to_access(group), NULL, 0);
206 static void snd_ctl_led_clean(struct snd_card *card)
209 struct list_head *controls;
210 struct snd_ctl_led *lctl;
212 for (group = 0; group < MAX_LED; group++) {
213 controls = &snd_ctl_led_controls[group];
215 list_for_each_entry(lctl, controls, list)
216 if (!card || lctl->card == card) {
217 list_del(&lctl->list);
224 static void snd_ctl_led_register(struct snd_card *card)
226 struct snd_kcontrol *kctl;
229 if (snd_BUG_ON(card->number < 0 ||
230 card->number >= ARRAY_SIZE(snd_ctl_led_card_valid)))
232 mutex_lock(&snd_ctl_led_mutex);
233 snd_ctl_led_card_valid[card->number] = true;
234 mutex_unlock(&snd_ctl_led_mutex);
235 /* the register callback is already called with held card->controls_rwsem */
236 list_for_each_entry(kctl, &card->controls, list)
237 for (ioff = 0; ioff < kctl->count; ioff++)
238 snd_ctl_led_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, kctl, ioff);
239 snd_ctl_led_refresh();
242 static void snd_ctl_led_disconnect(struct snd_card *card)
244 mutex_lock(&snd_ctl_led_mutex);
245 snd_ctl_led_card_valid[card->number] = false;
246 snd_ctl_led_clean(card);
247 mutex_unlock(&snd_ctl_led_mutex);
248 snd_ctl_led_refresh();
252 * Control layer registration
254 static struct snd_ctl_layer_ops snd_ctl_led_lops = {
255 .module_name = SND_CTL_LAYER_MODULE_LED,
256 .lregister = snd_ctl_led_register,
257 .ldisconnect = snd_ctl_led_disconnect,
258 .lnotify = snd_ctl_led_notify,
261 static int __init snd_ctl_led_init(void)
265 for (group = 0; group < MAX_LED; group++)
266 INIT_LIST_HEAD(&snd_ctl_led_controls[group]);
267 snd_ctl_register_layer(&snd_ctl_led_lops);
271 static void __exit snd_ctl_led_exit(void)
273 snd_ctl_disconnect_layer(&snd_ctl_led_lops);
274 snd_ctl_led_clean(NULL);
277 module_init(snd_ctl_led_init)
278 module_exit(snd_ctl_led_exit)