ALSA: control - add sysfs support to the LED trigger module
[linux-block.git] / sound / core / control_led.c
CommitLineData
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
13MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>");
14MODULE_DESCRIPTION("ALSA control interface to LED trigger code.");
15MODULE_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
cb17fe00
JK
20enum snd_ctl_led_mode {
21 MODE_FOLLOW_MUTE = 0,
22 MODE_FOLLOW_ROUTE,
23 MODE_OFF,
24 MODE_ON,
25};
26
22d8de62 27struct snd_ctl_led {
cb17fe00
JK
28 struct device dev;
29 struct list_head controls;
30 const char *name;
31 unsigned int group;
32 enum led_audio trigger_type;
33 enum snd_ctl_led_mode mode;
34};
35
36struct snd_ctl_led_ctl {
22d8de62
JK
37 struct list_head list;
38 struct snd_card *card;
39 unsigned int access;
40 struct snd_kcontrol *kctl;
41 unsigned int index_offset;
42};
43
44static DEFINE_MUTEX(snd_ctl_led_mutex);
22d8de62 45static bool snd_ctl_led_card_valid[SNDRV_CARDS];
cb17fe00
JK
46static struct snd_ctl_led snd_ctl_leds[MAX_LED] = {
47 {
48 .name = "speaker",
49 .group = (SNDRV_CTL_ELEM_ACCESS_SPK_LED >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1,
50 .trigger_type = LED_AUDIO_MUTE,
51 .mode = MODE_FOLLOW_MUTE,
52 },
53 {
54 .name = "mic",
55 .group = (SNDRV_CTL_ELEM_ACCESS_MIC_LED >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1,
56 .trigger_type = LED_AUDIO_MICMUTE,
57 .mode = MODE_FOLLOW_MUTE,
58 },
59};
22d8de62
JK
60
61#define UPDATE_ROUTE(route, cb) \
62 do { \
63 int route2 = (cb); \
64 if (route2 >= 0) \
65 route = route < 0 ? route2 : (route | route2); \
66 } while (0)
67
68static inline unsigned int access_to_group(unsigned int access)
69{
70 return ((access & SNDRV_CTL_ELEM_ACCESS_LED_MASK) >>
71 SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1;
72}
73
74static inline unsigned int group_to_access(unsigned int group)
75{
76 return (group + 1) << SNDRV_CTL_ELEM_ACCESS_LED_SHIFT;
77}
78
cb17fe00 79static struct snd_ctl_led *snd_ctl_led_get_by_access(unsigned int access)
22d8de62
JK
80{
81 unsigned int group = access_to_group(access);
82 if (group >= MAX_LED)
83 return NULL;
cb17fe00 84 return &snd_ctl_leds[group];
22d8de62
JK
85}
86
cb17fe00 87static int snd_ctl_led_get(struct snd_ctl_led_ctl *lctl)
22d8de62
JK
88{
89 struct snd_kcontrol *kctl = lctl->kctl;
90 struct snd_ctl_elem_info info;
91 struct snd_ctl_elem_value value;
92 unsigned int i;
93 int result;
94
95 memset(&info, 0, sizeof(info));
96 info.id = kctl->id;
97 info.id.index += lctl->index_offset;
98 info.id.numid += lctl->index_offset;
99 result = kctl->info(kctl, &info);
100 if (result < 0)
101 return -1;
102 memset(&value, 0, sizeof(value));
103 value.id = info.id;
104 result = kctl->get(kctl, &value);
105 if (result < 0)
106 return -1;
107 if (info.type == SNDRV_CTL_ELEM_TYPE_BOOLEAN ||
108 info.type == SNDRV_CTL_ELEM_TYPE_INTEGER) {
109 for (i = 0; i < info.count; i++)
110 if (value.value.integer.value[i] != info.value.integer.min)
111 return 1;
112 } else if (info.type == SNDRV_CTL_ELEM_TYPE_INTEGER64) {
113 for (i = 0; i < info.count; i++)
114 if (value.value.integer64.value[i] != info.value.integer64.min)
115 return 1;
116 }
117 return 0;
118}
119
120static void snd_ctl_led_set_state(struct snd_card *card, unsigned int access,
121 struct snd_kcontrol *kctl, unsigned int ioff)
122{
cb17fe00
JK
123 struct snd_ctl_led *led;
124 struct snd_ctl_led_ctl *lctl;
22d8de62
JK
125 int route;
126 bool found;
127
cb17fe00
JK
128 led = snd_ctl_led_get_by_access(access);
129 if (!led)
22d8de62 130 return;
22d8de62
JK
131 route = -1;
132 found = false;
133 mutex_lock(&snd_ctl_led_mutex);
134 /* the card may not be registered (active) at this point */
135 if (card && !snd_ctl_led_card_valid[card->number]) {
136 mutex_unlock(&snd_ctl_led_mutex);
137 return;
138 }
cb17fe00 139 list_for_each_entry(lctl, &led->controls, list) {
22d8de62
JK
140 if (lctl->kctl == kctl && lctl->index_offset == ioff)
141 found = true;
142 UPDATE_ROUTE(route, snd_ctl_led_get(lctl));
143 }
144 if (!found && kctl && card) {
145 lctl = kzalloc(sizeof(*lctl), GFP_KERNEL);
146 if (lctl) {
147 lctl->card = card;
148 lctl->access = access;
149 lctl->kctl = kctl;
150 lctl->index_offset = ioff;
cb17fe00 151 list_add(&lctl->list, &led->controls);
22d8de62
JK
152 UPDATE_ROUTE(route, snd_ctl_led_get(lctl));
153 }
154 }
155 mutex_unlock(&snd_ctl_led_mutex);
cb17fe00
JK
156 switch (led->mode) {
157 case MODE_OFF: route = 1; break;
158 case MODE_ON: route = 0; break;
159 case MODE_FOLLOW_ROUTE: if (route >= 0) route ^= 1; break;
160 case MODE_FOLLOW_MUTE: /* noop */ break;
161 }
22d8de62 162 if (route >= 0)
cb17fe00 163 ledtrig_audio_set(led->trigger_type, route ? LED_OFF : LED_ON);
22d8de62
JK
164}
165
cb17fe00 166static struct snd_ctl_led_ctl *snd_ctl_led_find(struct snd_kcontrol *kctl, unsigned int ioff)
22d8de62
JK
167{
168 struct list_head *controls;
cb17fe00 169 struct snd_ctl_led_ctl *lctl;
22d8de62
JK
170 unsigned int group;
171
172 for (group = 0; group < MAX_LED; group++) {
cb17fe00 173 controls = &snd_ctl_leds[group].controls;
22d8de62
JK
174 list_for_each_entry(lctl, controls, list)
175 if (lctl->kctl == kctl && lctl->index_offset == ioff)
176 return lctl;
177 }
178 return NULL;
179}
180
181static unsigned int snd_ctl_led_remove(struct snd_kcontrol *kctl, unsigned int ioff,
182 unsigned int access)
183{
cb17fe00 184 struct snd_ctl_led_ctl *lctl;
22d8de62
JK
185 unsigned int ret = 0;
186
187 mutex_lock(&snd_ctl_led_mutex);
188 lctl = snd_ctl_led_find(kctl, ioff);
189 if (lctl && (access == 0 || access != lctl->access)) {
190 ret = lctl->access;
191 list_del(&lctl->list);
192 kfree(lctl);
193 }
194 mutex_unlock(&snd_ctl_led_mutex);
195 return ret;
196}
197
198static void snd_ctl_led_notify(struct snd_card *card, unsigned int mask,
199 struct snd_kcontrol *kctl, unsigned int ioff)
200{
201 struct snd_kcontrol_volatile *vd;
202 unsigned int access, access2;
203
204 if (mask == SNDRV_CTL_EVENT_MASK_REMOVE) {
205 access = snd_ctl_led_remove(kctl, ioff, 0);
206 if (access)
207 snd_ctl_led_set_state(card, access, NULL, 0);
208 } else if (mask & SNDRV_CTL_EVENT_MASK_INFO) {
209 vd = &kctl->vd[ioff];
210 access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK;
211 access2 = snd_ctl_led_remove(kctl, ioff, access);
212 if (access2)
213 snd_ctl_led_set_state(card, access2, NULL, 0);
214 if (access)
215 snd_ctl_led_set_state(card, access, kctl, ioff);
216 } else if ((mask & (SNDRV_CTL_EVENT_MASK_ADD |
217 SNDRV_CTL_EVENT_MASK_VALUE)) != 0) {
218 vd = &kctl->vd[ioff];
219 access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK;
220 if (access)
221 snd_ctl_led_set_state(card, access, kctl, ioff);
222 }
223}
224
225static void snd_ctl_led_refresh(void)
226{
227 unsigned int group;
228
229 for (group = 0; group < MAX_LED; group++)
230 snd_ctl_led_set_state(NULL, group_to_access(group), NULL, 0);
231}
232
233static void snd_ctl_led_clean(struct snd_card *card)
234{
235 unsigned int group;
cb17fe00
JK
236 struct snd_ctl_led *led;
237 struct snd_ctl_led_ctl *lctl;
22d8de62
JK
238
239 for (group = 0; group < MAX_LED; group++) {
cb17fe00 240 led = &snd_ctl_leds[group];
22d8de62 241repeat:
cb17fe00 242 list_for_each_entry(lctl, &led->controls, list)
22d8de62
JK
243 if (!card || lctl->card == card) {
244 list_del(&lctl->list);
245 kfree(lctl);
246 goto repeat;
247 }
248 }
249}
250
251static void snd_ctl_led_register(struct snd_card *card)
252{
253 struct snd_kcontrol *kctl;
254 unsigned int ioff;
255
256 if (snd_BUG_ON(card->number < 0 ||
257 card->number >= ARRAY_SIZE(snd_ctl_led_card_valid)))
258 return;
259 mutex_lock(&snd_ctl_led_mutex);
260 snd_ctl_led_card_valid[card->number] = true;
261 mutex_unlock(&snd_ctl_led_mutex);
262 /* the register callback is already called with held card->controls_rwsem */
263 list_for_each_entry(kctl, &card->controls, list)
264 for (ioff = 0; ioff < kctl->count; ioff++)
265 snd_ctl_led_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, kctl, ioff);
266 snd_ctl_led_refresh();
267}
268
269static void snd_ctl_led_disconnect(struct snd_card *card)
270{
271 mutex_lock(&snd_ctl_led_mutex);
272 snd_ctl_led_card_valid[card->number] = false;
273 snd_ctl_led_clean(card);
274 mutex_unlock(&snd_ctl_led_mutex);
275 snd_ctl_led_refresh();
276}
277
cb17fe00
JK
278/*
279 * sysfs
280 */
281
282static ssize_t show_mode(struct device *dev,
283 struct device_attribute *attr, char *buf)
284{
285 struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev);
286 const char *str;
287
288 switch (led->mode) {
289 case MODE_FOLLOW_MUTE: str = "follow-mute"; break;
290 case MODE_FOLLOW_ROUTE: str = "follow-route"; break;
291 case MODE_ON: str = "on"; break;
292 case MODE_OFF: str = "off"; break;
293 }
294 return sprintf(buf, "%s\n", str);
295}
296
297static ssize_t store_mode(struct device *dev, struct device_attribute *attr,
298 const char *buf, size_t count)
299{
300 struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev);
301 char _buf[16];
302 size_t l = min(count, sizeof(_buf) - 1) + 1;
303 enum snd_ctl_led_mode mode;
304
305 memcpy(_buf, buf, l);
306 _buf[l] = '\0';
307 if (strstr(_buf, "mute"))
308 mode = MODE_FOLLOW_MUTE;
309 else if (strstr(_buf, "route"))
310 mode = MODE_FOLLOW_ROUTE;
311 else if (strncmp(_buf, "off", 3) == 0 || strncmp(_buf, "0", 1) == 0)
312 mode = MODE_OFF;
313 else if (strncmp(_buf, "on", 2) == 0 || strncmp(_buf, "1", 1) == 0)
314 mode = MODE_ON;
315 else
316 return count;
317
318 mutex_lock(&snd_ctl_led_mutex);
319 led->mode = mode;
320 mutex_unlock(&snd_ctl_led_mutex);
321
322 snd_ctl_led_set_state(NULL, group_to_access(led->group), NULL, 0);
323 return count;
324}
325
326static ssize_t show_brightness(struct device *dev,
327 struct device_attribute *attr, char *buf)
328{
329 struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev);
330
331 return sprintf(buf, "%u\n", ledtrig_audio_get(led->trigger_type));
332}
333
334static DEVICE_ATTR(mode, 0644, show_mode, store_mode);
335static DEVICE_ATTR(brightness, 0444, show_brightness, NULL);
336
337static struct attribute *snd_ctl_led_dev_attrs[] = {
338 &dev_attr_mode.attr,
339 &dev_attr_brightness.attr,
340 NULL,
341};
342
343static const struct attribute_group snd_ctl_led_dev_attr_group = {
344 .attrs = snd_ctl_led_dev_attrs,
345};
346
347static const struct attribute_group *snd_ctl_led_dev_attr_groups[] = {
348 &snd_ctl_led_dev_attr_group,
349 NULL,
350};
351
352static struct device snd_ctl_led_dev;
353
22d8de62
JK
354/*
355 * Control layer registration
356 */
357static struct snd_ctl_layer_ops snd_ctl_led_lops = {
358 .module_name = SND_CTL_LAYER_MODULE_LED,
359 .lregister = snd_ctl_led_register,
360 .ldisconnect = snd_ctl_led_disconnect,
361 .lnotify = snd_ctl_led_notify,
362};
363
364static int __init snd_ctl_led_init(void)
365{
cb17fe00 366 struct snd_ctl_led *led;
22d8de62
JK
367 unsigned int group;
368
cb17fe00
JK
369 device_initialize(&snd_ctl_led_dev);
370 snd_ctl_led_dev.class = sound_class;
371 dev_set_name(&snd_ctl_led_dev, "ctl-led");
372 if (device_add(&snd_ctl_led_dev)) {
373 put_device(&snd_ctl_led_dev);
374 return -ENOMEM;
375 }
376 for (group = 0; group < MAX_LED; group++) {
377 led = &snd_ctl_leds[group];
378 INIT_LIST_HEAD(&led->controls);
379 device_initialize(&led->dev);
380 led->dev.parent = &snd_ctl_led_dev;
381 led->dev.groups = snd_ctl_led_dev_attr_groups;
382 dev_set_name(&led->dev, led->name);
383 if (device_add(&led->dev)) {
384 put_device(&led->dev);
385 for (; group > 0; group--) {
386 led = &snd_ctl_leds[group];
387 device_del(&led->dev);
388 }
389 device_del(&snd_ctl_led_dev);
390 return -ENOMEM;
391 }
392 }
22d8de62
JK
393 snd_ctl_register_layer(&snd_ctl_led_lops);
394 return 0;
395}
396
397static void __exit snd_ctl_led_exit(void)
398{
cb17fe00
JK
399 struct snd_ctl_led *led;
400 unsigned int group;
401
402 for (group = 0; group < MAX_LED; group++) {
403 led = &snd_ctl_leds[group];
404 device_del(&led->dev);
405 }
406 device_del(&snd_ctl_led_dev);
22d8de62
JK
407 snd_ctl_disconnect_layer(&snd_ctl_led_lops);
408 snd_ctl_led_clean(NULL);
409}
410
411module_init(snd_ctl_led_init)
412module_exit(snd_ctl_led_exit)