forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
795 lines
20 KiB
795 lines
20 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* LED state routines for driver control interface |
|
* Copyright (c) 2021 by Jaroslav Kysela <[email protected]> |
|
*/ |
|
|
|
#include <linux/slab.h> |
|
#include <linux/module.h> |
|
#include <linux/leds.h> |
|
#include <sound/core.h> |
|
#include <sound/control.h> |
|
|
|
MODULE_AUTHOR("Jaroslav Kysela <[email protected]>"); |
|
MODULE_DESCRIPTION("ALSA control interface to LED trigger code."); |
|
MODULE_LICENSE("GPL"); |
|
|
|
#define MAX_LED (((SNDRV_CTL_ELEM_ACCESS_MIC_LED - SNDRV_CTL_ELEM_ACCESS_SPK_LED) \ |
|
>> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) + 1) |
|
|
|
#define to_led_card_dev(_dev) \ |
|
container_of(_dev, struct snd_ctl_led_card, dev) |
|
|
|
enum snd_ctl_led_mode { |
|
MODE_FOLLOW_MUTE = 0, |
|
MODE_FOLLOW_ROUTE, |
|
MODE_OFF, |
|
MODE_ON, |
|
}; |
|
|
|
struct snd_ctl_led_card { |
|
struct device dev; |
|
int number; |
|
struct snd_ctl_led *led; |
|
}; |
|
|
|
struct snd_ctl_led { |
|
struct device dev; |
|
struct list_head controls; |
|
const char *name; |
|
unsigned int group; |
|
enum led_audio trigger_type; |
|
enum snd_ctl_led_mode mode; |
|
struct snd_ctl_led_card *cards[SNDRV_CARDS]; |
|
}; |
|
|
|
struct snd_ctl_led_ctl { |
|
struct list_head list; |
|
struct snd_card *card; |
|
unsigned int access; |
|
struct snd_kcontrol *kctl; |
|
unsigned int index_offset; |
|
}; |
|
|
|
static DEFINE_MUTEX(snd_ctl_led_mutex); |
|
static bool snd_ctl_led_card_valid[SNDRV_CARDS]; |
|
static struct snd_ctl_led snd_ctl_leds[MAX_LED] = { |
|
{ |
|
.name = "speaker", |
|
.group = (SNDRV_CTL_ELEM_ACCESS_SPK_LED >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1, |
|
.trigger_type = LED_AUDIO_MUTE, |
|
.mode = MODE_FOLLOW_MUTE, |
|
}, |
|
{ |
|
.name = "mic", |
|
.group = (SNDRV_CTL_ELEM_ACCESS_MIC_LED >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1, |
|
.trigger_type = LED_AUDIO_MICMUTE, |
|
.mode = MODE_FOLLOW_MUTE, |
|
}, |
|
}; |
|
|
|
static void snd_ctl_led_sysfs_add(struct snd_card *card); |
|
static void snd_ctl_led_sysfs_remove(struct snd_card *card); |
|
|
|
#define UPDATE_ROUTE(route, cb) \ |
|
do { \ |
|
int route2 = (cb); \ |
|
if (route2 >= 0) \ |
|
route = route < 0 ? route2 : (route | route2); \ |
|
} while (0) |
|
|
|
static inline unsigned int access_to_group(unsigned int access) |
|
{ |
|
return ((access & SNDRV_CTL_ELEM_ACCESS_LED_MASK) >> |
|
SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1; |
|
} |
|
|
|
static inline unsigned int group_to_access(unsigned int group) |
|
{ |
|
return (group + 1) << SNDRV_CTL_ELEM_ACCESS_LED_SHIFT; |
|
} |
|
|
|
static struct snd_ctl_led *snd_ctl_led_get_by_access(unsigned int access) |
|
{ |
|
unsigned int group = access_to_group(access); |
|
if (group >= MAX_LED) |
|
return NULL; |
|
return &snd_ctl_leds[group]; |
|
} |
|
|
|
/* |
|
* A note for callers: |
|
* The two static variables info and value are protected using snd_ctl_led_mutex. |
|
*/ |
|
static int snd_ctl_led_get(struct snd_ctl_led_ctl *lctl) |
|
{ |
|
static struct snd_ctl_elem_info info; |
|
static struct snd_ctl_elem_value value; |
|
struct snd_kcontrol *kctl = lctl->kctl; |
|
unsigned int i; |
|
int result; |
|
|
|
memset(&info, 0, sizeof(info)); |
|
info.id = kctl->id; |
|
info.id.index += lctl->index_offset; |
|
info.id.numid += lctl->index_offset; |
|
result = kctl->info(kctl, &info); |
|
if (result < 0) |
|
return -1; |
|
memset(&value, 0, sizeof(value)); |
|
value.id = info.id; |
|
result = kctl->get(kctl, &value); |
|
if (result < 0) |
|
return -1; |
|
if (info.type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || |
|
info.type == SNDRV_CTL_ELEM_TYPE_INTEGER) { |
|
for (i = 0; i < info.count; i++) |
|
if (value.value.integer.value[i] != info.value.integer.min) |
|
return 1; |
|
} else if (info.type == SNDRV_CTL_ELEM_TYPE_INTEGER64) { |
|
for (i = 0; i < info.count; i++) |
|
if (value.value.integer64.value[i] != info.value.integer64.min) |
|
return 1; |
|
} |
|
return 0; |
|
} |
|
|
|
static void snd_ctl_led_set_state(struct snd_card *card, unsigned int access, |
|
struct snd_kcontrol *kctl, unsigned int ioff) |
|
{ |
|
struct snd_ctl_led *led; |
|
struct snd_ctl_led_ctl *lctl; |
|
int route; |
|
bool found; |
|
|
|
led = snd_ctl_led_get_by_access(access); |
|
if (!led) |
|
return; |
|
route = -1; |
|
found = false; |
|
mutex_lock(&snd_ctl_led_mutex); |
|
/* the card may not be registered (active) at this point */ |
|
if (card && !snd_ctl_led_card_valid[card->number]) { |
|
mutex_unlock(&snd_ctl_led_mutex); |
|
return; |
|
} |
|
list_for_each_entry(lctl, &led->controls, list) { |
|
if (lctl->kctl == kctl && lctl->index_offset == ioff) |
|
found = true; |
|
UPDATE_ROUTE(route, snd_ctl_led_get(lctl)); |
|
} |
|
if (!found && kctl && card) { |
|
lctl = kzalloc(sizeof(*lctl), GFP_KERNEL); |
|
if (lctl) { |
|
lctl->card = card; |
|
lctl->access = access; |
|
lctl->kctl = kctl; |
|
lctl->index_offset = ioff; |
|
list_add(&lctl->list, &led->controls); |
|
UPDATE_ROUTE(route, snd_ctl_led_get(lctl)); |
|
} |
|
} |
|
mutex_unlock(&snd_ctl_led_mutex); |
|
switch (led->mode) { |
|
case MODE_OFF: route = 1; break; |
|
case MODE_ON: route = 0; break; |
|
case MODE_FOLLOW_ROUTE: if (route >= 0) route ^= 1; break; |
|
case MODE_FOLLOW_MUTE: /* noop */ break; |
|
} |
|
if (route >= 0) |
|
ledtrig_audio_set(led->trigger_type, route ? LED_OFF : LED_ON); |
|
} |
|
|
|
static struct snd_ctl_led_ctl *snd_ctl_led_find(struct snd_kcontrol *kctl, unsigned int ioff) |
|
{ |
|
struct list_head *controls; |
|
struct snd_ctl_led_ctl *lctl; |
|
unsigned int group; |
|
|
|
for (group = 0; group < MAX_LED; group++) { |
|
controls = &snd_ctl_leds[group].controls; |
|
list_for_each_entry(lctl, controls, list) |
|
if (lctl->kctl == kctl && lctl->index_offset == ioff) |
|
return lctl; |
|
} |
|
return NULL; |
|
} |
|
|
|
static unsigned int snd_ctl_led_remove(struct snd_kcontrol *kctl, unsigned int ioff, |
|
unsigned int access) |
|
{ |
|
struct snd_ctl_led_ctl *lctl; |
|
unsigned int ret = 0; |
|
|
|
mutex_lock(&snd_ctl_led_mutex); |
|
lctl = snd_ctl_led_find(kctl, ioff); |
|
if (lctl && (access == 0 || access != lctl->access)) { |
|
ret = lctl->access; |
|
list_del(&lctl->list); |
|
kfree(lctl); |
|
} |
|
mutex_unlock(&snd_ctl_led_mutex); |
|
return ret; |
|
} |
|
|
|
static void snd_ctl_led_notify(struct snd_card *card, unsigned int mask, |
|
struct snd_kcontrol *kctl, unsigned int ioff) |
|
{ |
|
struct snd_kcontrol_volatile *vd; |
|
unsigned int access, access2; |
|
|
|
if (mask == SNDRV_CTL_EVENT_MASK_REMOVE) { |
|
access = snd_ctl_led_remove(kctl, ioff, 0); |
|
if (access) |
|
snd_ctl_led_set_state(card, access, NULL, 0); |
|
} else if (mask & SNDRV_CTL_EVENT_MASK_INFO) { |
|
vd = &kctl->vd[ioff]; |
|
access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; |
|
access2 = snd_ctl_led_remove(kctl, ioff, access); |
|
if (access2) |
|
snd_ctl_led_set_state(card, access2, NULL, 0); |
|
if (access) |
|
snd_ctl_led_set_state(card, access, kctl, ioff); |
|
} else if ((mask & (SNDRV_CTL_EVENT_MASK_ADD | |
|
SNDRV_CTL_EVENT_MASK_VALUE)) != 0) { |
|
vd = &kctl->vd[ioff]; |
|
access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; |
|
if (access) |
|
snd_ctl_led_set_state(card, access, kctl, ioff); |
|
} |
|
} |
|
|
|
static int snd_ctl_led_set_id(int card_number, struct snd_ctl_elem_id *id, |
|
unsigned int group, bool set) |
|
{ |
|
struct snd_card *card; |
|
struct snd_kcontrol *kctl; |
|
struct snd_kcontrol_volatile *vd; |
|
unsigned int ioff, access, new_access; |
|
int err = 0; |
|
|
|
card = snd_card_ref(card_number); |
|
if (card) { |
|
down_write(&card->controls_rwsem); |
|
kctl = snd_ctl_find_id(card, id); |
|
if (kctl) { |
|
ioff = snd_ctl_get_ioff(kctl, id); |
|
vd = &kctl->vd[ioff]; |
|
access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; |
|
if (access != 0 && access != group_to_access(group)) { |
|
err = -EXDEV; |
|
goto unlock; |
|
} |
|
new_access = vd->access & ~SNDRV_CTL_ELEM_ACCESS_LED_MASK; |
|
if (set) |
|
new_access |= group_to_access(group); |
|
if (new_access != vd->access) { |
|
vd->access = new_access; |
|
snd_ctl_led_notify(card, SNDRV_CTL_EVENT_MASK_INFO, kctl, ioff); |
|
} |
|
} else { |
|
err = -ENOENT; |
|
} |
|
unlock: |
|
up_write(&card->controls_rwsem); |
|
snd_card_unref(card); |
|
} else { |
|
err = -ENXIO; |
|
} |
|
return err; |
|
} |
|
|
|
static void snd_ctl_led_refresh(void) |
|
{ |
|
unsigned int group; |
|
|
|
for (group = 0; group < MAX_LED; group++) |
|
snd_ctl_led_set_state(NULL, group_to_access(group), NULL, 0); |
|
} |
|
|
|
static void snd_ctl_led_ctl_destroy(struct snd_ctl_led_ctl *lctl) |
|
{ |
|
list_del(&lctl->list); |
|
kfree(lctl); |
|
} |
|
|
|
static void snd_ctl_led_clean(struct snd_card *card) |
|
{ |
|
unsigned int group; |
|
struct snd_ctl_led *led; |
|
struct snd_ctl_led_ctl *lctl; |
|
|
|
for (group = 0; group < MAX_LED; group++) { |
|
led = &snd_ctl_leds[group]; |
|
repeat: |
|
list_for_each_entry(lctl, &led->controls, list) |
|
if (!card || lctl->card == card) { |
|
snd_ctl_led_ctl_destroy(lctl); |
|
goto repeat; |
|
} |
|
} |
|
} |
|
|
|
static int snd_ctl_led_reset(int card_number, unsigned int group) |
|
{ |
|
struct snd_card *card; |
|
struct snd_ctl_led *led; |
|
struct snd_ctl_led_ctl *lctl; |
|
struct snd_kcontrol_volatile *vd; |
|
bool change = false; |
|
|
|
card = snd_card_ref(card_number); |
|
if (!card) |
|
return -ENXIO; |
|
|
|
mutex_lock(&snd_ctl_led_mutex); |
|
if (!snd_ctl_led_card_valid[card_number]) { |
|
mutex_unlock(&snd_ctl_led_mutex); |
|
snd_card_unref(card); |
|
return -ENXIO; |
|
} |
|
led = &snd_ctl_leds[group]; |
|
repeat: |
|
list_for_each_entry(lctl, &led->controls, list) |
|
if (lctl->card == card) { |
|
vd = &lctl->kctl->vd[lctl->index_offset]; |
|
vd->access &= ~group_to_access(group); |
|
snd_ctl_led_ctl_destroy(lctl); |
|
change = true; |
|
goto repeat; |
|
} |
|
mutex_unlock(&snd_ctl_led_mutex); |
|
if (change) |
|
snd_ctl_led_set_state(NULL, group_to_access(group), NULL, 0); |
|
snd_card_unref(card); |
|
return 0; |
|
} |
|
|
|
static void snd_ctl_led_register(struct snd_card *card) |
|
{ |
|
struct snd_kcontrol *kctl; |
|
unsigned int ioff; |
|
|
|
if (snd_BUG_ON(card->number < 0 || |
|
card->number >= ARRAY_SIZE(snd_ctl_led_card_valid))) |
|
return; |
|
mutex_lock(&snd_ctl_led_mutex); |
|
snd_ctl_led_card_valid[card->number] = true; |
|
mutex_unlock(&snd_ctl_led_mutex); |
|
/* the register callback is already called with held card->controls_rwsem */ |
|
list_for_each_entry(kctl, &card->controls, list) |
|
for (ioff = 0; ioff < kctl->count; ioff++) |
|
snd_ctl_led_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, kctl, ioff); |
|
snd_ctl_led_refresh(); |
|
snd_ctl_led_sysfs_add(card); |
|
} |
|
|
|
static void snd_ctl_led_disconnect(struct snd_card *card) |
|
{ |
|
snd_ctl_led_sysfs_remove(card); |
|
mutex_lock(&snd_ctl_led_mutex); |
|
snd_ctl_led_card_valid[card->number] = false; |
|
snd_ctl_led_clean(card); |
|
mutex_unlock(&snd_ctl_led_mutex); |
|
snd_ctl_led_refresh(); |
|
} |
|
|
|
static void snd_ctl_led_card_release(struct device *dev) |
|
{ |
|
struct snd_ctl_led_card *led_card = to_led_card_dev(dev); |
|
|
|
kfree(led_card); |
|
} |
|
|
|
static void snd_ctl_led_release(struct device *dev) |
|
{ |
|
} |
|
|
|
static void snd_ctl_led_dev_release(struct device *dev) |
|
{ |
|
} |
|
|
|
/* |
|
* sysfs |
|
*/ |
|
|
|
static ssize_t mode_show(struct device *dev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); |
|
const char *str = NULL; |
|
|
|
switch (led->mode) { |
|
case MODE_FOLLOW_MUTE: str = "follow-mute"; break; |
|
case MODE_FOLLOW_ROUTE: str = "follow-route"; break; |
|
case MODE_ON: str = "on"; break; |
|
case MODE_OFF: str = "off"; break; |
|
} |
|
return sysfs_emit(buf, "%s\n", str); |
|
} |
|
|
|
static ssize_t mode_store(struct device *dev, |
|
struct device_attribute *attr, |
|
const char *buf, size_t count) |
|
{ |
|
struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); |
|
char _buf[16]; |
|
size_t l = min(count, sizeof(_buf) - 1); |
|
enum snd_ctl_led_mode mode; |
|
|
|
memcpy(_buf, buf, l); |
|
_buf[l] = '\0'; |
|
if (strstr(_buf, "mute")) |
|
mode = MODE_FOLLOW_MUTE; |
|
else if (strstr(_buf, "route")) |
|
mode = MODE_FOLLOW_ROUTE; |
|
else if (strncmp(_buf, "off", 3) == 0 || strncmp(_buf, "0", 1) == 0) |
|
mode = MODE_OFF; |
|
else if (strncmp(_buf, "on", 2) == 0 || strncmp(_buf, "1", 1) == 0) |
|
mode = MODE_ON; |
|
else |
|
return count; |
|
|
|
mutex_lock(&snd_ctl_led_mutex); |
|
led->mode = mode; |
|
mutex_unlock(&snd_ctl_led_mutex); |
|
|
|
snd_ctl_led_set_state(NULL, group_to_access(led->group), NULL, 0); |
|
return count; |
|
} |
|
|
|
static ssize_t brightness_show(struct device *dev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); |
|
|
|
return sysfs_emit(buf, "%u\n", ledtrig_audio_get(led->trigger_type)); |
|
} |
|
|
|
static DEVICE_ATTR_RW(mode); |
|
static DEVICE_ATTR_RO(brightness); |
|
|
|
static struct attribute *snd_ctl_led_dev_attrs[] = { |
|
&dev_attr_mode.attr, |
|
&dev_attr_brightness.attr, |
|
NULL, |
|
}; |
|
|
|
static const struct attribute_group snd_ctl_led_dev_attr_group = { |
|
.attrs = snd_ctl_led_dev_attrs, |
|
}; |
|
|
|
static const struct attribute_group *snd_ctl_led_dev_attr_groups[] = { |
|
&snd_ctl_led_dev_attr_group, |
|
NULL, |
|
}; |
|
|
|
static char *find_eos(char *s) |
|
{ |
|
while (*s && *s != ',') |
|
s++; |
|
if (*s) |
|
s++; |
|
return s; |
|
} |
|
|
|
static char *parse_uint(char *s, unsigned int *val) |
|
{ |
|
unsigned long long res; |
|
if (kstrtoull(s, 10, &res)) |
|
res = 0; |
|
*val = res; |
|
return find_eos(s); |
|
} |
|
|
|
static char *parse_string(char *s, char *val, size_t val_size) |
|
{ |
|
if (*s == '"' || *s == '\'') { |
|
char c = *s; |
|
s++; |
|
while (*s && *s != c) { |
|
if (val_size > 1) { |
|
*val++ = *s; |
|
val_size--; |
|
} |
|
s++; |
|
} |
|
} else { |
|
while (*s && *s != ',') { |
|
if (val_size > 1) { |
|
*val++ = *s; |
|
val_size--; |
|
} |
|
s++; |
|
} |
|
} |
|
*val = '\0'; |
|
if (*s) |
|
s++; |
|
return s; |
|
} |
|
|
|
static char *parse_iface(char *s, snd_ctl_elem_iface_t *val) |
|
{ |
|
if (!strncasecmp(s, "card", 4)) |
|
*val = SNDRV_CTL_ELEM_IFACE_CARD; |
|
else if (!strncasecmp(s, "mixer", 5)) |
|
*val = SNDRV_CTL_ELEM_IFACE_MIXER; |
|
return find_eos(s); |
|
} |
|
|
|
/* |
|
* These types of input strings are accepted: |
|
* |
|
* unsigned integer - numid (equivaled to numid=UINT) |
|
* string - basic mixer name (equivalent to iface=MIXER,name=STR) |
|
* numid=UINT |
|
* [iface=MIXER,][device=UINT,][subdevice=UINT,]name=STR[,index=UINT] |
|
*/ |
|
static ssize_t set_led_id(struct snd_ctl_led_card *led_card, const char *buf, size_t count, |
|
bool attach) |
|
{ |
|
char buf2[256], *s, *os; |
|
size_t len = max(sizeof(s) - 1, count); |
|
struct snd_ctl_elem_id id; |
|
int err; |
|
|
|
strncpy(buf2, buf, len); |
|
buf2[len] = '\0'; |
|
memset(&id, 0, sizeof(id)); |
|
id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; |
|
s = buf2; |
|
while (*s) { |
|
os = s; |
|
if (!strncasecmp(s, "numid=", 6)) { |
|
s = parse_uint(s + 6, &id.numid); |
|
} else if (!strncasecmp(s, "iface=", 6)) { |
|
s = parse_iface(s + 6, &id.iface); |
|
} else if (!strncasecmp(s, "device=", 7)) { |
|
s = parse_uint(s + 7, &id.device); |
|
} else if (!strncasecmp(s, "subdevice=", 10)) { |
|
s = parse_uint(s + 10, &id.subdevice); |
|
} else if (!strncasecmp(s, "name=", 5)) { |
|
s = parse_string(s + 5, id.name, sizeof(id.name)); |
|
} else if (!strncasecmp(s, "index=", 6)) { |
|
s = parse_uint(s + 6, &id.index); |
|
} else if (s == buf2) { |
|
while (*s) { |
|
if (*s < '0' || *s > '9') |
|
break; |
|
s++; |
|
} |
|
if (*s == '\0') |
|
parse_uint(buf2, &id.numid); |
|
else { |
|
for (; *s >= ' '; s++); |
|
*s = '\0'; |
|
strscpy(id.name, buf2, sizeof(id.name)); |
|
} |
|
break; |
|
} |
|
if (*s == ',') |
|
s++; |
|
if (s == os) |
|
break; |
|
} |
|
|
|
err = snd_ctl_led_set_id(led_card->number, &id, led_card->led->group, attach); |
|
if (err < 0) |
|
return err; |
|
|
|
return count; |
|
} |
|
|
|
static ssize_t attach_store(struct device *dev, |
|
struct device_attribute *attr, |
|
const char *buf, size_t count) |
|
{ |
|
struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); |
|
return set_led_id(led_card, buf, count, true); |
|
} |
|
|
|
static ssize_t detach_store(struct device *dev, |
|
struct device_attribute *attr, |
|
const char *buf, size_t count) |
|
{ |
|
struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); |
|
return set_led_id(led_card, buf, count, false); |
|
} |
|
|
|
static ssize_t reset_store(struct device *dev, |
|
struct device_attribute *attr, |
|
const char *buf, size_t count) |
|
{ |
|
struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); |
|
int err; |
|
|
|
if (count > 0 && buf[0] == '1') { |
|
err = snd_ctl_led_reset(led_card->number, led_card->led->group); |
|
if (err < 0) |
|
return err; |
|
} |
|
return count; |
|
} |
|
|
|
static ssize_t list_show(struct device *dev, |
|
struct device_attribute *attr, char *buf) |
|
{ |
|
struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); |
|
struct snd_card *card; |
|
struct snd_ctl_led_ctl *lctl; |
|
size_t l = 0; |
|
|
|
card = snd_card_ref(led_card->number); |
|
if (!card) |
|
return -ENXIO; |
|
down_read(&card->controls_rwsem); |
|
mutex_lock(&snd_ctl_led_mutex); |
|
if (snd_ctl_led_card_valid[led_card->number]) { |
|
list_for_each_entry(lctl, &led_card->led->controls, list) { |
|
if (lctl->card != card) |
|
continue; |
|
if (l) |
|
l += sysfs_emit_at(buf, l, " "); |
|
l += sysfs_emit_at(buf, l, "%u", |
|
lctl->kctl->id.numid + lctl->index_offset); |
|
} |
|
} |
|
mutex_unlock(&snd_ctl_led_mutex); |
|
up_read(&card->controls_rwsem); |
|
snd_card_unref(card); |
|
return l; |
|
} |
|
|
|
static DEVICE_ATTR_WO(attach); |
|
static DEVICE_ATTR_WO(detach); |
|
static DEVICE_ATTR_WO(reset); |
|
static DEVICE_ATTR_RO(list); |
|
|
|
static struct attribute *snd_ctl_led_card_attrs[] = { |
|
&dev_attr_attach.attr, |
|
&dev_attr_detach.attr, |
|
&dev_attr_reset.attr, |
|
&dev_attr_list.attr, |
|
NULL, |
|
}; |
|
|
|
static const struct attribute_group snd_ctl_led_card_attr_group = { |
|
.attrs = snd_ctl_led_card_attrs, |
|
}; |
|
|
|
static const struct attribute_group *snd_ctl_led_card_attr_groups[] = { |
|
&snd_ctl_led_card_attr_group, |
|
NULL, |
|
}; |
|
|
|
static struct device snd_ctl_led_dev; |
|
|
|
static void snd_ctl_led_sysfs_add(struct snd_card *card) |
|
{ |
|
unsigned int group; |
|
struct snd_ctl_led_card *led_card; |
|
struct snd_ctl_led *led; |
|
char link_name[32]; |
|
|
|
for (group = 0; group < MAX_LED; group++) { |
|
led = &snd_ctl_leds[group]; |
|
led_card = kzalloc(sizeof(*led_card), GFP_KERNEL); |
|
if (!led_card) |
|
goto cerr2; |
|
led_card->number = card->number; |
|
led_card->led = led; |
|
device_initialize(&led_card->dev); |
|
led_card->dev.release = snd_ctl_led_card_release; |
|
if (dev_set_name(&led_card->dev, "card%d", card->number) < 0) |
|
goto cerr; |
|
led_card->dev.parent = &led->dev; |
|
led_card->dev.groups = snd_ctl_led_card_attr_groups; |
|
if (device_add(&led_card->dev)) |
|
goto cerr; |
|
led->cards[card->number] = led_card; |
|
snprintf(link_name, sizeof(link_name), "led-%s", led->name); |
|
WARN(sysfs_create_link(&card->ctl_dev.kobj, &led_card->dev.kobj, link_name), |
|
"can't create symlink to controlC%i device\n", card->number); |
|
WARN(sysfs_create_link(&led_card->dev.kobj, &card->card_dev.kobj, "card"), |
|
"can't create symlink to card%i\n", card->number); |
|
|
|
continue; |
|
cerr: |
|
put_device(&led_card->dev); |
|
cerr2: |
|
printk(KERN_ERR "snd_ctl_led: unable to add card%d", card->number); |
|
} |
|
} |
|
|
|
static void snd_ctl_led_sysfs_remove(struct snd_card *card) |
|
{ |
|
unsigned int group; |
|
struct snd_ctl_led_card *led_card; |
|
struct snd_ctl_led *led; |
|
char link_name[32]; |
|
|
|
for (group = 0; group < MAX_LED; group++) { |
|
led = &snd_ctl_leds[group]; |
|
led_card = led->cards[card->number]; |
|
if (!led_card) |
|
continue; |
|
snprintf(link_name, sizeof(link_name), "led-%s", led->name); |
|
sysfs_remove_link(&card->ctl_dev.kobj, link_name); |
|
sysfs_remove_link(&led_card->dev.kobj, "card"); |
|
device_unregister(&led_card->dev); |
|
led->cards[card->number] = NULL; |
|
} |
|
} |
|
|
|
/* |
|
* Control layer registration |
|
*/ |
|
static struct snd_ctl_layer_ops snd_ctl_led_lops = { |
|
.module_name = SND_CTL_LAYER_MODULE_LED, |
|
.lregister = snd_ctl_led_register, |
|
.ldisconnect = snd_ctl_led_disconnect, |
|
.lnotify = snd_ctl_led_notify, |
|
}; |
|
|
|
static int __init snd_ctl_led_init(void) |
|
{ |
|
struct snd_ctl_led *led; |
|
unsigned int group; |
|
|
|
device_initialize(&snd_ctl_led_dev); |
|
snd_ctl_led_dev.class = sound_class; |
|
snd_ctl_led_dev.release = snd_ctl_led_dev_release; |
|
dev_set_name(&snd_ctl_led_dev, "ctl-led"); |
|
if (device_add(&snd_ctl_led_dev)) { |
|
put_device(&snd_ctl_led_dev); |
|
return -ENOMEM; |
|
} |
|
for (group = 0; group < MAX_LED; group++) { |
|
led = &snd_ctl_leds[group]; |
|
INIT_LIST_HEAD(&led->controls); |
|
device_initialize(&led->dev); |
|
led->dev.parent = &snd_ctl_led_dev; |
|
led->dev.release = snd_ctl_led_release; |
|
led->dev.groups = snd_ctl_led_dev_attr_groups; |
|
dev_set_name(&led->dev, led->name); |
|
if (device_add(&led->dev)) { |
|
put_device(&led->dev); |
|
for (; group > 0; group--) { |
|
led = &snd_ctl_leds[group - 1]; |
|
device_unregister(&led->dev); |
|
} |
|
device_unregister(&snd_ctl_led_dev); |
|
return -ENOMEM; |
|
} |
|
} |
|
snd_ctl_register_layer(&snd_ctl_led_lops); |
|
return 0; |
|
} |
|
|
|
static void __exit snd_ctl_led_exit(void) |
|
{ |
|
struct snd_ctl_led *led; |
|
struct snd_card *card; |
|
unsigned int group, card_number; |
|
|
|
snd_ctl_disconnect_layer(&snd_ctl_led_lops); |
|
for (card_number = 0; card_number < SNDRV_CARDS; card_number++) { |
|
if (!snd_ctl_led_card_valid[card_number]) |
|
continue; |
|
card = snd_card_ref(card_number); |
|
if (card) { |
|
snd_ctl_led_sysfs_remove(card); |
|
snd_card_unref(card); |
|
} |
|
} |
|
for (group = 0; group < MAX_LED; group++) { |
|
led = &snd_ctl_leds[group]; |
|
device_unregister(&led->dev); |
|
} |
|
device_unregister(&snd_ctl_led_dev); |
|
snd_ctl_led_clean(NULL); |
|
} |
|
|
|
module_init(snd_ctl_led_init) |
|
module_exit(snd_ctl_led_exit)
|
|
|