ALSA: HDA - remove the custom implementation for the audio LED trigger
[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
20struct snd_ctl_led {
21 struct list_head list;
22 struct snd_card *card;
23 unsigned int access;
24 struct snd_kcontrol *kctl;
25 unsigned int index_offset;
26};
27
28static DEFINE_MUTEX(snd_ctl_led_mutex);
29static struct list_head snd_ctl_led_controls[MAX_LED];
30static bool snd_ctl_led_card_valid[SNDRV_CARDS];
31
32#define UPDATE_ROUTE(route, cb) \
33 do { \
34 int route2 = (cb); \
35 if (route2 >= 0) \
36 route = route < 0 ? route2 : (route | route2); \
37 } while (0)
38
39static inline unsigned int access_to_group(unsigned int access)
40{
41 return ((access & SNDRV_CTL_ELEM_ACCESS_LED_MASK) >>
42 SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1;
43}
44
45static inline unsigned int group_to_access(unsigned int group)
46{
47 return (group + 1) << SNDRV_CTL_ELEM_ACCESS_LED_SHIFT;
48}
49
50static struct list_head *snd_ctl_led_controls_by_access(unsigned int access)
51{
52 unsigned int group = access_to_group(access);
53 if (group >= MAX_LED)
54 return NULL;
55 return &snd_ctl_led_controls[group];
56}
57
58static int snd_ctl_led_get(struct snd_ctl_led *lctl)
59{
60 struct snd_kcontrol *kctl = lctl->kctl;
61 struct snd_ctl_elem_info info;
62 struct snd_ctl_elem_value value;
63 unsigned int i;
64 int result;
65
66 memset(&info, 0, sizeof(info));
67 info.id = kctl->id;
68 info.id.index += lctl->index_offset;
69 info.id.numid += lctl->index_offset;
70 result = kctl->info(kctl, &info);
71 if (result < 0)
72 return -1;
73 memset(&value, 0, sizeof(value));
74 value.id = info.id;
75 result = kctl->get(kctl, &value);
76 if (result < 0)
77 return -1;
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)
82 return 1;
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)
86 return 1;
87 }
88 return 0;
89}
90
91static void snd_ctl_led_set_state(struct snd_card *card, unsigned int access,
92 struct snd_kcontrol *kctl, unsigned int ioff)
93{
94 struct list_head *controls;
95 struct snd_ctl_led *lctl;
96 enum led_audio led_trigger_type;
97 int route;
98 bool found;
99
100 controls = snd_ctl_led_controls_by_access(access);
101 if (!controls)
102 return;
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;
107 } else {
108 return;
109 }
110 route = -1;
111 found = false;
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);
116 return;
117 }
118 list_for_each_entry(lctl, controls, list) {
119 if (lctl->kctl == kctl && lctl->index_offset == ioff)
120 found = true;
121 UPDATE_ROUTE(route, snd_ctl_led_get(lctl));
122 }
123 if (!found && kctl && card) {
124 lctl = kzalloc(sizeof(*lctl), GFP_KERNEL);
125 if (lctl) {
126 lctl->card = card;
127 lctl->access = access;
128 lctl->kctl = kctl;
129 lctl->index_offset = ioff;
130 list_add(&lctl->list, controls);
131 UPDATE_ROUTE(route, snd_ctl_led_get(lctl));
132 }
133 }
134 mutex_unlock(&snd_ctl_led_mutex);
135 if (route >= 0)
136 ledtrig_audio_set(led_trigger_type, route ? LED_OFF : LED_ON);
137}
138
139static struct snd_ctl_led *snd_ctl_led_find(struct snd_kcontrol *kctl, unsigned int ioff)
140{
141 struct list_head *controls;
142 struct snd_ctl_led *lctl;
143 unsigned int group;
144
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)
149 return lctl;
150 }
151 return NULL;
152}
153
154static unsigned int snd_ctl_led_remove(struct snd_kcontrol *kctl, unsigned int ioff,
155 unsigned int access)
156{
157 struct snd_ctl_led *lctl;
158 unsigned int ret = 0;
159
160 mutex_lock(&snd_ctl_led_mutex);
161 lctl = snd_ctl_led_find(kctl, ioff);
162 if (lctl && (access == 0 || access != lctl->access)) {
163 ret = lctl->access;
164 list_del(&lctl->list);
165 kfree(lctl);
166 }
167 mutex_unlock(&snd_ctl_led_mutex);
168 return ret;
169}
170
171static void snd_ctl_led_notify(struct snd_card *card, unsigned int mask,
172 struct snd_kcontrol *kctl, unsigned int ioff)
173{
174 struct snd_kcontrol_volatile *vd;
175 unsigned int access, access2;
176
177 if (mask == SNDRV_CTL_EVENT_MASK_REMOVE) {
178 access = snd_ctl_led_remove(kctl, ioff, 0);
179 if (access)
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);
185 if (access2)
186 snd_ctl_led_set_state(card, access2, NULL, 0);
187 if (access)
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;
193 if (access)
194 snd_ctl_led_set_state(card, access, kctl, ioff);
195 }
196}
197
198static void snd_ctl_led_refresh(void)
199{
200 unsigned int group;
201
202 for (group = 0; group < MAX_LED; group++)
203 snd_ctl_led_set_state(NULL, group_to_access(group), NULL, 0);
204}
205
206static void snd_ctl_led_clean(struct snd_card *card)
207{
208 unsigned int group;
209 struct list_head *controls;
210 struct snd_ctl_led *lctl;
211
212 for (group = 0; group < MAX_LED; group++) {
213 controls = &snd_ctl_led_controls[group];
214repeat:
215 list_for_each_entry(lctl, controls, list)
216 if (!card || lctl->card == card) {
217 list_del(&lctl->list);
218 kfree(lctl);
219 goto repeat;
220 }
221 }
222}
223
224static void snd_ctl_led_register(struct snd_card *card)
225{
226 struct snd_kcontrol *kctl;
227 unsigned int ioff;
228
229 if (snd_BUG_ON(card->number < 0 ||
230 card->number >= ARRAY_SIZE(snd_ctl_led_card_valid)))
231 return;
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();
240}
241
242static void snd_ctl_led_disconnect(struct snd_card *card)
243{
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();
249}
250
251/*
252 * Control layer registration
253 */
254static 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,
259};
260
261static int __init snd_ctl_led_init(void)
262{
263 unsigned int group;
264
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);
268 return 0;
269}
270
271static void __exit snd_ctl_led_exit(void)
272{
273 snd_ctl_disconnect_layer(&snd_ctl_led_lops);
274 snd_ctl_led_clean(NULL);
275}
276
277module_init(snd_ctl_led_init)
278module_exit(snd_ctl_led_exit)