Skip to content

Commit 22d8de6

Browse files
perexgtiwai
authored andcommitted
ALSA: control - add generic LED trigger module as the new control layer
The recent laptops have usually two LEDs assigned to reflect the speaker and microphone mute state. This implementation adds a tiny layer on top of the control API which calculates the state for those LEDs using the driver callbacks. Two new access flags are introduced to describe the controls which affects the audio path settings (an easy code change for drivers). The LED resource can be shared with multiple sound cards with this code. The user space controls may be added to the state chain on demand, too. This code should replace the LED code in the HDA driver and add a possibility to easy extend the other drivers (ASoC codecs etc.). Signed-off-by: Jaroslav Kysela <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Takashi Iwai <[email protected]>
1 parent 3f0638a commit 22d8de6

File tree

5 files changed

+307
-2
lines changed

5 files changed

+307
-2
lines changed

include/sound/control.h

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,20 @@ typedef int (snd_kcontrol_tlv_rw_t)(struct snd_kcontrol *kcontrol,
2424

2525
/* internal flag for skipping validations */
2626
#ifdef CONFIG_SND_CTL_VALIDATION
27-
#define SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK (1 << 27)
27+
#define SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK (1 << 24)
2828
#define snd_ctl_skip_validation(info) \
2929
((info)->access & SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK)
3030
#else
3131
#define SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK 0
3232
#define snd_ctl_skip_validation(info) true
3333
#endif
3434

35+
/* kernel only - LED bits */
36+
#define SNDRV_CTL_ELEM_ACCESS_LED_SHIFT 25
37+
#define SNDRV_CTL_ELEM_ACCESS_LED_MASK (7<<25) /* kernel three bits - LED group */
38+
#define SNDRV_CTL_ELEM_ACCESS_SPK_LED (1<<25) /* kernel speaker (output) LED flag */
39+
#define SNDRV_CTL_ELEM_ACCESS_MIC_LED (2<<25) /* kernel microphone (input) LED flag */
40+
3541
enum {
3642
SNDRV_CTL_TLV_OP_READ = 0,
3743
SNDRV_CTL_TLV_OP_WRITE = 1,
@@ -265,6 +271,17 @@ int snd_ctl_apply_vmaster_followers(struct snd_kcontrol *kctl,
265271
void *arg),
266272
void *arg);
267273

274+
/*
275+
* Control LED trigger layer
276+
*/
277+
#define SND_CTL_LAYER_MODULE_LED "snd-ctl-led"
278+
279+
#if IS_MODULE(CONFIG_SND_CTL_LED)
280+
static inline int snd_ctl_led_request(void) { return snd_ctl_request_layer(SND_CTL_LAYER_MODULE_LED); }
281+
#else
282+
static inline int snd_ctl_led_request(void) { return 0; }
283+
#endif
284+
268285
/*
269286
* Helper functions for jack-detection controls
270287
*/

sound/core/Kconfig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,4 +203,10 @@ config SND_DMA_SGBUF
203203
def_bool y
204204
depends on X86
205205

206+
config SND_CTL_LED
207+
tristate
208+
select NEW_LEDS if SND_CTL_LED
209+
select LEDS_TRIGGERS if SND_CTL_LED
210+
select LEDS_TRIGGER_AUDIO if SND_CTL_LED
211+
206212
source "sound/core/seq/Kconfig"

sound/core/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ CFLAGS_pcm_native.o := -I$(src)
2727

2828
snd-pcm-dmaengine-objs := pcm_dmaengine.o
2929

30+
snd-ctl-led-objs := control_led.o
3031
snd-rawmidi-objs := rawmidi.o
3132
snd-timer-objs := timer.o
3233
snd-hrtimer-objs := hrtimer.o
@@ -37,6 +38,7 @@ snd-seq-device-objs := seq_device.o
3738
snd-compress-objs := compress_offload.o
3839

3940
obj-$(CONFIG_SND) += snd.o
41+
obj-$(CONFIG_SND_CTL_LED) += snd-ctl-led.o
4042
obj-$(CONFIG_SND_HWDEP) += snd-hwdep.o
4143
obj-$(CONFIG_SND_TIMER) += snd-timer.o
4244
obj-$(CONFIG_SND_HRTIMER) += snd-hrtimer.o

sound/core/control.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,
278278
SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE |
279279
SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND |
280280
SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK |
281+
SNDRV_CTL_ELEM_ACCESS_LED_MASK |
281282
SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK);
282283

283284
err = snd_ctl_new(&kctl, count, access, NULL);
@@ -1047,7 +1048,8 @@ static int snd_ctl_elem_info_user(struct snd_ctl_file *ctl,
10471048
if (result < 0)
10481049
return result;
10491050
/* drop internal access flags */
1050-
info.access &= ~SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK;
1051+
info.access &= ~(SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK|
1052+
SNDRV_CTL_ELEM_ACCESS_LED_MASK);
10511053
if (copy_to_user(_info, &info, sizeof(info)))
10521054
return -EFAULT;
10531055
return result;

sound/core/control_led.c

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
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 <[email protected]>
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 <[email protected]>");
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+
20+
struct 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+
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];
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+
39+
static 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+
45+
static inline unsigned int group_to_access(unsigned int group)
46+
{
47+
return (group + 1) << SNDRV_CTL_ELEM_ACCESS_LED_SHIFT;
48+
}
49+
50+
static 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+
58+
static 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+
91+
static 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+
139+
static 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+
154+
static 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+
171+
static 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+
198+
static 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+
206+
static 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];
214+
repeat:
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+
224+
static 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+
242+
static 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+
*/
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,
259+
};
260+
261+
static 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+
271+
static 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+
277+
module_init(snd_ctl_led_init)
278+
module_exit(snd_ctl_led_exit)

0 commit comments

Comments
 (0)