162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * LED state routines for driver control interface 462306a36Sopenharmony_ci * Copyright (c) 2021 by Jaroslav Kysela <perex@perex.cz> 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/slab.h> 862306a36Sopenharmony_ci#include <linux/module.h> 962306a36Sopenharmony_ci#include <linux/leds.h> 1062306a36Sopenharmony_ci#include <sound/core.h> 1162306a36Sopenharmony_ci#include <sound/control.h> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ciMODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>"); 1462306a36Sopenharmony_ciMODULE_DESCRIPTION("ALSA control interface to LED trigger code."); 1562306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#define MAX_LED (((SNDRV_CTL_ELEM_ACCESS_MIC_LED - SNDRV_CTL_ELEM_ACCESS_SPK_LED) \ 1862306a36Sopenharmony_ci >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) + 1) 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#define to_led_card_dev(_dev) \ 2162306a36Sopenharmony_ci container_of(_dev, struct snd_ctl_led_card, dev) 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_cienum snd_ctl_led_mode { 2462306a36Sopenharmony_ci MODE_FOLLOW_MUTE = 0, 2562306a36Sopenharmony_ci MODE_FOLLOW_ROUTE, 2662306a36Sopenharmony_ci MODE_OFF, 2762306a36Sopenharmony_ci MODE_ON, 2862306a36Sopenharmony_ci}; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_cistruct snd_ctl_led_card { 3162306a36Sopenharmony_ci struct device dev; 3262306a36Sopenharmony_ci int number; 3362306a36Sopenharmony_ci struct snd_ctl_led *led; 3462306a36Sopenharmony_ci}; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_cistruct snd_ctl_led { 3762306a36Sopenharmony_ci struct device dev; 3862306a36Sopenharmony_ci struct list_head controls; 3962306a36Sopenharmony_ci const char *name; 4062306a36Sopenharmony_ci unsigned int group; 4162306a36Sopenharmony_ci enum led_audio trigger_type; 4262306a36Sopenharmony_ci enum snd_ctl_led_mode mode; 4362306a36Sopenharmony_ci struct snd_ctl_led_card *cards[SNDRV_CARDS]; 4462306a36Sopenharmony_ci}; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistruct snd_ctl_led_ctl { 4762306a36Sopenharmony_ci struct list_head list; 4862306a36Sopenharmony_ci struct snd_card *card; 4962306a36Sopenharmony_ci unsigned int access; 5062306a36Sopenharmony_ci struct snd_kcontrol *kctl; 5162306a36Sopenharmony_ci unsigned int index_offset; 5262306a36Sopenharmony_ci}; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_cistatic DEFINE_MUTEX(snd_ctl_led_mutex); 5562306a36Sopenharmony_cistatic bool snd_ctl_led_card_valid[SNDRV_CARDS]; 5662306a36Sopenharmony_cistatic struct snd_ctl_led snd_ctl_leds[MAX_LED] = { 5762306a36Sopenharmony_ci { 5862306a36Sopenharmony_ci .name = "speaker", 5962306a36Sopenharmony_ci .group = (SNDRV_CTL_ELEM_ACCESS_SPK_LED >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1, 6062306a36Sopenharmony_ci .trigger_type = LED_AUDIO_MUTE, 6162306a36Sopenharmony_ci .mode = MODE_FOLLOW_MUTE, 6262306a36Sopenharmony_ci }, 6362306a36Sopenharmony_ci { 6462306a36Sopenharmony_ci .name = "mic", 6562306a36Sopenharmony_ci .group = (SNDRV_CTL_ELEM_ACCESS_MIC_LED >> SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1, 6662306a36Sopenharmony_ci .trigger_type = LED_AUDIO_MICMUTE, 6762306a36Sopenharmony_ci .mode = MODE_FOLLOW_MUTE, 6862306a36Sopenharmony_ci }, 6962306a36Sopenharmony_ci}; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_cistatic void snd_ctl_led_sysfs_add(struct snd_card *card); 7262306a36Sopenharmony_cistatic void snd_ctl_led_sysfs_remove(struct snd_card *card); 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_ci#define UPDATE_ROUTE(route, cb) \ 7562306a36Sopenharmony_ci do { \ 7662306a36Sopenharmony_ci int route2 = (cb); \ 7762306a36Sopenharmony_ci if (route2 >= 0) \ 7862306a36Sopenharmony_ci route = route < 0 ? route2 : (route | route2); \ 7962306a36Sopenharmony_ci } while (0) 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_cistatic inline unsigned int access_to_group(unsigned int access) 8262306a36Sopenharmony_ci{ 8362306a36Sopenharmony_ci return ((access & SNDRV_CTL_ELEM_ACCESS_LED_MASK) >> 8462306a36Sopenharmony_ci SNDRV_CTL_ELEM_ACCESS_LED_SHIFT) - 1; 8562306a36Sopenharmony_ci} 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_cistatic inline unsigned int group_to_access(unsigned int group) 8862306a36Sopenharmony_ci{ 8962306a36Sopenharmony_ci return (group + 1) << SNDRV_CTL_ELEM_ACCESS_LED_SHIFT; 9062306a36Sopenharmony_ci} 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_cistatic struct snd_ctl_led *snd_ctl_led_get_by_access(unsigned int access) 9362306a36Sopenharmony_ci{ 9462306a36Sopenharmony_ci unsigned int group = access_to_group(access); 9562306a36Sopenharmony_ci if (group >= MAX_LED) 9662306a36Sopenharmony_ci return NULL; 9762306a36Sopenharmony_ci return &snd_ctl_leds[group]; 9862306a36Sopenharmony_ci} 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci/* 10162306a36Sopenharmony_ci * A note for callers: 10262306a36Sopenharmony_ci * The two static variables info and value are protected using snd_ctl_led_mutex. 10362306a36Sopenharmony_ci */ 10462306a36Sopenharmony_cistatic int snd_ctl_led_get(struct snd_ctl_led_ctl *lctl) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci static struct snd_ctl_elem_info info; 10762306a36Sopenharmony_ci static struct snd_ctl_elem_value value; 10862306a36Sopenharmony_ci struct snd_kcontrol *kctl = lctl->kctl; 10962306a36Sopenharmony_ci unsigned int i; 11062306a36Sopenharmony_ci int result; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci memset(&info, 0, sizeof(info)); 11362306a36Sopenharmony_ci info.id = kctl->id; 11462306a36Sopenharmony_ci info.id.index += lctl->index_offset; 11562306a36Sopenharmony_ci info.id.numid += lctl->index_offset; 11662306a36Sopenharmony_ci result = kctl->info(kctl, &info); 11762306a36Sopenharmony_ci if (result < 0) 11862306a36Sopenharmony_ci return -1; 11962306a36Sopenharmony_ci memset(&value, 0, sizeof(value)); 12062306a36Sopenharmony_ci value.id = info.id; 12162306a36Sopenharmony_ci result = kctl->get(kctl, &value); 12262306a36Sopenharmony_ci if (result < 0) 12362306a36Sopenharmony_ci return -1; 12462306a36Sopenharmony_ci if (info.type == SNDRV_CTL_ELEM_TYPE_BOOLEAN || 12562306a36Sopenharmony_ci info.type == SNDRV_CTL_ELEM_TYPE_INTEGER) { 12662306a36Sopenharmony_ci for (i = 0; i < info.count; i++) 12762306a36Sopenharmony_ci if (value.value.integer.value[i] != info.value.integer.min) 12862306a36Sopenharmony_ci return 1; 12962306a36Sopenharmony_ci } else if (info.type == SNDRV_CTL_ELEM_TYPE_INTEGER64) { 13062306a36Sopenharmony_ci for (i = 0; i < info.count; i++) 13162306a36Sopenharmony_ci if (value.value.integer64.value[i] != info.value.integer64.min) 13262306a36Sopenharmony_ci return 1; 13362306a36Sopenharmony_ci } 13462306a36Sopenharmony_ci return 0; 13562306a36Sopenharmony_ci} 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_cistatic void snd_ctl_led_set_state(struct snd_card *card, unsigned int access, 13862306a36Sopenharmony_ci struct snd_kcontrol *kctl, unsigned int ioff) 13962306a36Sopenharmony_ci{ 14062306a36Sopenharmony_ci struct snd_ctl_led *led; 14162306a36Sopenharmony_ci struct snd_ctl_led_ctl *lctl; 14262306a36Sopenharmony_ci int route; 14362306a36Sopenharmony_ci bool found; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci led = snd_ctl_led_get_by_access(access); 14662306a36Sopenharmony_ci if (!led) 14762306a36Sopenharmony_ci return; 14862306a36Sopenharmony_ci route = -1; 14962306a36Sopenharmony_ci found = false; 15062306a36Sopenharmony_ci mutex_lock(&snd_ctl_led_mutex); 15162306a36Sopenharmony_ci /* the card may not be registered (active) at this point */ 15262306a36Sopenharmony_ci if (card && !snd_ctl_led_card_valid[card->number]) { 15362306a36Sopenharmony_ci mutex_unlock(&snd_ctl_led_mutex); 15462306a36Sopenharmony_ci return; 15562306a36Sopenharmony_ci } 15662306a36Sopenharmony_ci list_for_each_entry(lctl, &led->controls, list) { 15762306a36Sopenharmony_ci if (lctl->kctl == kctl && lctl->index_offset == ioff) 15862306a36Sopenharmony_ci found = true; 15962306a36Sopenharmony_ci UPDATE_ROUTE(route, snd_ctl_led_get(lctl)); 16062306a36Sopenharmony_ci } 16162306a36Sopenharmony_ci if (!found && kctl && card) { 16262306a36Sopenharmony_ci lctl = kzalloc(sizeof(*lctl), GFP_KERNEL); 16362306a36Sopenharmony_ci if (lctl) { 16462306a36Sopenharmony_ci lctl->card = card; 16562306a36Sopenharmony_ci lctl->access = access; 16662306a36Sopenharmony_ci lctl->kctl = kctl; 16762306a36Sopenharmony_ci lctl->index_offset = ioff; 16862306a36Sopenharmony_ci list_add(&lctl->list, &led->controls); 16962306a36Sopenharmony_ci UPDATE_ROUTE(route, snd_ctl_led_get(lctl)); 17062306a36Sopenharmony_ci } 17162306a36Sopenharmony_ci } 17262306a36Sopenharmony_ci mutex_unlock(&snd_ctl_led_mutex); 17362306a36Sopenharmony_ci switch (led->mode) { 17462306a36Sopenharmony_ci case MODE_OFF: route = 1; break; 17562306a36Sopenharmony_ci case MODE_ON: route = 0; break; 17662306a36Sopenharmony_ci case MODE_FOLLOW_ROUTE: if (route >= 0) route ^= 1; break; 17762306a36Sopenharmony_ci case MODE_FOLLOW_MUTE: /* noop */ break; 17862306a36Sopenharmony_ci } 17962306a36Sopenharmony_ci if (route >= 0) 18062306a36Sopenharmony_ci ledtrig_audio_set(led->trigger_type, route ? LED_OFF : LED_ON); 18162306a36Sopenharmony_ci} 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_cistatic struct snd_ctl_led_ctl *snd_ctl_led_find(struct snd_kcontrol *kctl, unsigned int ioff) 18462306a36Sopenharmony_ci{ 18562306a36Sopenharmony_ci struct list_head *controls; 18662306a36Sopenharmony_ci struct snd_ctl_led_ctl *lctl; 18762306a36Sopenharmony_ci unsigned int group; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci for (group = 0; group < MAX_LED; group++) { 19062306a36Sopenharmony_ci controls = &snd_ctl_leds[group].controls; 19162306a36Sopenharmony_ci list_for_each_entry(lctl, controls, list) 19262306a36Sopenharmony_ci if (lctl->kctl == kctl && lctl->index_offset == ioff) 19362306a36Sopenharmony_ci return lctl; 19462306a36Sopenharmony_ci } 19562306a36Sopenharmony_ci return NULL; 19662306a36Sopenharmony_ci} 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_cistatic unsigned int snd_ctl_led_remove(struct snd_kcontrol *kctl, unsigned int ioff, 19962306a36Sopenharmony_ci unsigned int access) 20062306a36Sopenharmony_ci{ 20162306a36Sopenharmony_ci struct snd_ctl_led_ctl *lctl; 20262306a36Sopenharmony_ci unsigned int ret = 0; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci mutex_lock(&snd_ctl_led_mutex); 20562306a36Sopenharmony_ci lctl = snd_ctl_led_find(kctl, ioff); 20662306a36Sopenharmony_ci if (lctl && (access == 0 || access != lctl->access)) { 20762306a36Sopenharmony_ci ret = lctl->access; 20862306a36Sopenharmony_ci list_del(&lctl->list); 20962306a36Sopenharmony_ci kfree(lctl); 21062306a36Sopenharmony_ci } 21162306a36Sopenharmony_ci mutex_unlock(&snd_ctl_led_mutex); 21262306a36Sopenharmony_ci return ret; 21362306a36Sopenharmony_ci} 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_cistatic void snd_ctl_led_notify(struct snd_card *card, unsigned int mask, 21662306a36Sopenharmony_ci struct snd_kcontrol *kctl, unsigned int ioff) 21762306a36Sopenharmony_ci{ 21862306a36Sopenharmony_ci struct snd_kcontrol_volatile *vd; 21962306a36Sopenharmony_ci unsigned int access, access2; 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci if (mask == SNDRV_CTL_EVENT_MASK_REMOVE) { 22262306a36Sopenharmony_ci access = snd_ctl_led_remove(kctl, ioff, 0); 22362306a36Sopenharmony_ci if (access) 22462306a36Sopenharmony_ci snd_ctl_led_set_state(card, access, NULL, 0); 22562306a36Sopenharmony_ci } else if (mask & SNDRV_CTL_EVENT_MASK_INFO) { 22662306a36Sopenharmony_ci vd = &kctl->vd[ioff]; 22762306a36Sopenharmony_ci access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; 22862306a36Sopenharmony_ci access2 = snd_ctl_led_remove(kctl, ioff, access); 22962306a36Sopenharmony_ci if (access2) 23062306a36Sopenharmony_ci snd_ctl_led_set_state(card, access2, NULL, 0); 23162306a36Sopenharmony_ci if (access) 23262306a36Sopenharmony_ci snd_ctl_led_set_state(card, access, kctl, ioff); 23362306a36Sopenharmony_ci } else if ((mask & (SNDRV_CTL_EVENT_MASK_ADD | 23462306a36Sopenharmony_ci SNDRV_CTL_EVENT_MASK_VALUE)) != 0) { 23562306a36Sopenharmony_ci vd = &kctl->vd[ioff]; 23662306a36Sopenharmony_ci access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; 23762306a36Sopenharmony_ci if (access) 23862306a36Sopenharmony_ci snd_ctl_led_set_state(card, access, kctl, ioff); 23962306a36Sopenharmony_ci } 24062306a36Sopenharmony_ci} 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_cistatic int snd_ctl_led_set_id(int card_number, struct snd_ctl_elem_id *id, 24362306a36Sopenharmony_ci unsigned int group, bool set) 24462306a36Sopenharmony_ci{ 24562306a36Sopenharmony_ci struct snd_card *card; 24662306a36Sopenharmony_ci struct snd_kcontrol *kctl; 24762306a36Sopenharmony_ci struct snd_kcontrol_volatile *vd; 24862306a36Sopenharmony_ci unsigned int ioff, access, new_access; 24962306a36Sopenharmony_ci int err = 0; 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci card = snd_card_ref(card_number); 25262306a36Sopenharmony_ci if (card) { 25362306a36Sopenharmony_ci down_write(&card->controls_rwsem); 25462306a36Sopenharmony_ci kctl = snd_ctl_find_id_locked(card, id); 25562306a36Sopenharmony_ci if (kctl) { 25662306a36Sopenharmony_ci ioff = snd_ctl_get_ioff(kctl, id); 25762306a36Sopenharmony_ci vd = &kctl->vd[ioff]; 25862306a36Sopenharmony_ci access = vd->access & SNDRV_CTL_ELEM_ACCESS_LED_MASK; 25962306a36Sopenharmony_ci if (access != 0 && access != group_to_access(group)) { 26062306a36Sopenharmony_ci err = -EXDEV; 26162306a36Sopenharmony_ci goto unlock; 26262306a36Sopenharmony_ci } 26362306a36Sopenharmony_ci new_access = vd->access & ~SNDRV_CTL_ELEM_ACCESS_LED_MASK; 26462306a36Sopenharmony_ci if (set) 26562306a36Sopenharmony_ci new_access |= group_to_access(group); 26662306a36Sopenharmony_ci if (new_access != vd->access) { 26762306a36Sopenharmony_ci vd->access = new_access; 26862306a36Sopenharmony_ci snd_ctl_led_notify(card, SNDRV_CTL_EVENT_MASK_INFO, kctl, ioff); 26962306a36Sopenharmony_ci } 27062306a36Sopenharmony_ci } else { 27162306a36Sopenharmony_ci err = -ENOENT; 27262306a36Sopenharmony_ci } 27362306a36Sopenharmony_ciunlock: 27462306a36Sopenharmony_ci up_write(&card->controls_rwsem); 27562306a36Sopenharmony_ci snd_card_unref(card); 27662306a36Sopenharmony_ci } else { 27762306a36Sopenharmony_ci err = -ENXIO; 27862306a36Sopenharmony_ci } 27962306a36Sopenharmony_ci return err; 28062306a36Sopenharmony_ci} 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_cistatic void snd_ctl_led_refresh(void) 28362306a36Sopenharmony_ci{ 28462306a36Sopenharmony_ci unsigned int group; 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci for (group = 0; group < MAX_LED; group++) 28762306a36Sopenharmony_ci snd_ctl_led_set_state(NULL, group_to_access(group), NULL, 0); 28862306a36Sopenharmony_ci} 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_cistatic void snd_ctl_led_ctl_destroy(struct snd_ctl_led_ctl *lctl) 29162306a36Sopenharmony_ci{ 29262306a36Sopenharmony_ci list_del(&lctl->list); 29362306a36Sopenharmony_ci kfree(lctl); 29462306a36Sopenharmony_ci} 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_cistatic void snd_ctl_led_clean(struct snd_card *card) 29762306a36Sopenharmony_ci{ 29862306a36Sopenharmony_ci unsigned int group; 29962306a36Sopenharmony_ci struct snd_ctl_led *led; 30062306a36Sopenharmony_ci struct snd_ctl_led_ctl *lctl; 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci for (group = 0; group < MAX_LED; group++) { 30362306a36Sopenharmony_ci led = &snd_ctl_leds[group]; 30462306a36Sopenharmony_cirepeat: 30562306a36Sopenharmony_ci list_for_each_entry(lctl, &led->controls, list) 30662306a36Sopenharmony_ci if (!card || lctl->card == card) { 30762306a36Sopenharmony_ci snd_ctl_led_ctl_destroy(lctl); 30862306a36Sopenharmony_ci goto repeat; 30962306a36Sopenharmony_ci } 31062306a36Sopenharmony_ci } 31162306a36Sopenharmony_ci} 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_cistatic int snd_ctl_led_reset(int card_number, unsigned int group) 31462306a36Sopenharmony_ci{ 31562306a36Sopenharmony_ci struct snd_card *card; 31662306a36Sopenharmony_ci struct snd_ctl_led *led; 31762306a36Sopenharmony_ci struct snd_ctl_led_ctl *lctl; 31862306a36Sopenharmony_ci struct snd_kcontrol_volatile *vd; 31962306a36Sopenharmony_ci bool change = false; 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci card = snd_card_ref(card_number); 32262306a36Sopenharmony_ci if (!card) 32362306a36Sopenharmony_ci return -ENXIO; 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci mutex_lock(&snd_ctl_led_mutex); 32662306a36Sopenharmony_ci if (!snd_ctl_led_card_valid[card_number]) { 32762306a36Sopenharmony_ci mutex_unlock(&snd_ctl_led_mutex); 32862306a36Sopenharmony_ci snd_card_unref(card); 32962306a36Sopenharmony_ci return -ENXIO; 33062306a36Sopenharmony_ci } 33162306a36Sopenharmony_ci led = &snd_ctl_leds[group]; 33262306a36Sopenharmony_cirepeat: 33362306a36Sopenharmony_ci list_for_each_entry(lctl, &led->controls, list) 33462306a36Sopenharmony_ci if (lctl->card == card) { 33562306a36Sopenharmony_ci vd = &lctl->kctl->vd[lctl->index_offset]; 33662306a36Sopenharmony_ci vd->access &= ~group_to_access(group); 33762306a36Sopenharmony_ci snd_ctl_led_ctl_destroy(lctl); 33862306a36Sopenharmony_ci change = true; 33962306a36Sopenharmony_ci goto repeat; 34062306a36Sopenharmony_ci } 34162306a36Sopenharmony_ci mutex_unlock(&snd_ctl_led_mutex); 34262306a36Sopenharmony_ci if (change) 34362306a36Sopenharmony_ci snd_ctl_led_set_state(NULL, group_to_access(group), NULL, 0); 34462306a36Sopenharmony_ci snd_card_unref(card); 34562306a36Sopenharmony_ci return 0; 34662306a36Sopenharmony_ci} 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_cistatic void snd_ctl_led_register(struct snd_card *card) 34962306a36Sopenharmony_ci{ 35062306a36Sopenharmony_ci struct snd_kcontrol *kctl; 35162306a36Sopenharmony_ci unsigned int ioff; 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci if (snd_BUG_ON(card->number < 0 || 35462306a36Sopenharmony_ci card->number >= ARRAY_SIZE(snd_ctl_led_card_valid))) 35562306a36Sopenharmony_ci return; 35662306a36Sopenharmony_ci mutex_lock(&snd_ctl_led_mutex); 35762306a36Sopenharmony_ci snd_ctl_led_card_valid[card->number] = true; 35862306a36Sopenharmony_ci mutex_unlock(&snd_ctl_led_mutex); 35962306a36Sopenharmony_ci /* the register callback is already called with held card->controls_rwsem */ 36062306a36Sopenharmony_ci list_for_each_entry(kctl, &card->controls, list) 36162306a36Sopenharmony_ci for (ioff = 0; ioff < kctl->count; ioff++) 36262306a36Sopenharmony_ci snd_ctl_led_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, kctl, ioff); 36362306a36Sopenharmony_ci snd_ctl_led_refresh(); 36462306a36Sopenharmony_ci snd_ctl_led_sysfs_add(card); 36562306a36Sopenharmony_ci} 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_cistatic void snd_ctl_led_disconnect(struct snd_card *card) 36862306a36Sopenharmony_ci{ 36962306a36Sopenharmony_ci snd_ctl_led_sysfs_remove(card); 37062306a36Sopenharmony_ci mutex_lock(&snd_ctl_led_mutex); 37162306a36Sopenharmony_ci snd_ctl_led_card_valid[card->number] = false; 37262306a36Sopenharmony_ci snd_ctl_led_clean(card); 37362306a36Sopenharmony_ci mutex_unlock(&snd_ctl_led_mutex); 37462306a36Sopenharmony_ci snd_ctl_led_refresh(); 37562306a36Sopenharmony_ci} 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_cistatic void snd_ctl_led_card_release(struct device *dev) 37862306a36Sopenharmony_ci{ 37962306a36Sopenharmony_ci struct snd_ctl_led_card *led_card = to_led_card_dev(dev); 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci kfree(led_card); 38262306a36Sopenharmony_ci} 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_cistatic void snd_ctl_led_release(struct device *dev) 38562306a36Sopenharmony_ci{ 38662306a36Sopenharmony_ci} 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_cistatic void snd_ctl_led_dev_release(struct device *dev) 38962306a36Sopenharmony_ci{ 39062306a36Sopenharmony_ci} 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci/* 39362306a36Sopenharmony_ci * sysfs 39462306a36Sopenharmony_ci */ 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_cistatic ssize_t mode_show(struct device *dev, 39762306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 39862306a36Sopenharmony_ci{ 39962306a36Sopenharmony_ci struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); 40062306a36Sopenharmony_ci const char *str = NULL; 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci switch (led->mode) { 40362306a36Sopenharmony_ci case MODE_FOLLOW_MUTE: str = "follow-mute"; break; 40462306a36Sopenharmony_ci case MODE_FOLLOW_ROUTE: str = "follow-route"; break; 40562306a36Sopenharmony_ci case MODE_ON: str = "on"; break; 40662306a36Sopenharmony_ci case MODE_OFF: str = "off"; break; 40762306a36Sopenharmony_ci } 40862306a36Sopenharmony_ci return sysfs_emit(buf, "%s\n", str); 40962306a36Sopenharmony_ci} 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_cistatic ssize_t mode_store(struct device *dev, 41262306a36Sopenharmony_ci struct device_attribute *attr, 41362306a36Sopenharmony_ci const char *buf, size_t count) 41462306a36Sopenharmony_ci{ 41562306a36Sopenharmony_ci struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); 41662306a36Sopenharmony_ci char _buf[16]; 41762306a36Sopenharmony_ci size_t l = min(count, sizeof(_buf) - 1); 41862306a36Sopenharmony_ci enum snd_ctl_led_mode mode; 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_ci memcpy(_buf, buf, l); 42162306a36Sopenharmony_ci _buf[l] = '\0'; 42262306a36Sopenharmony_ci if (strstr(_buf, "mute")) 42362306a36Sopenharmony_ci mode = MODE_FOLLOW_MUTE; 42462306a36Sopenharmony_ci else if (strstr(_buf, "route")) 42562306a36Sopenharmony_ci mode = MODE_FOLLOW_ROUTE; 42662306a36Sopenharmony_ci else if (strncmp(_buf, "off", 3) == 0 || strncmp(_buf, "0", 1) == 0) 42762306a36Sopenharmony_ci mode = MODE_OFF; 42862306a36Sopenharmony_ci else if (strncmp(_buf, "on", 2) == 0 || strncmp(_buf, "1", 1) == 0) 42962306a36Sopenharmony_ci mode = MODE_ON; 43062306a36Sopenharmony_ci else 43162306a36Sopenharmony_ci return count; 43262306a36Sopenharmony_ci 43362306a36Sopenharmony_ci mutex_lock(&snd_ctl_led_mutex); 43462306a36Sopenharmony_ci led->mode = mode; 43562306a36Sopenharmony_ci mutex_unlock(&snd_ctl_led_mutex); 43662306a36Sopenharmony_ci 43762306a36Sopenharmony_ci snd_ctl_led_set_state(NULL, group_to_access(led->group), NULL, 0); 43862306a36Sopenharmony_ci return count; 43962306a36Sopenharmony_ci} 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_cistatic ssize_t brightness_show(struct device *dev, 44262306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 44362306a36Sopenharmony_ci{ 44462306a36Sopenharmony_ci struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci return sysfs_emit(buf, "%u\n", ledtrig_audio_get(led->trigger_type)); 44762306a36Sopenharmony_ci} 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_cistatic DEVICE_ATTR_RW(mode); 45062306a36Sopenharmony_cistatic DEVICE_ATTR_RO(brightness); 45162306a36Sopenharmony_ci 45262306a36Sopenharmony_cistatic struct attribute *snd_ctl_led_dev_attrs[] = { 45362306a36Sopenharmony_ci &dev_attr_mode.attr, 45462306a36Sopenharmony_ci &dev_attr_brightness.attr, 45562306a36Sopenharmony_ci NULL, 45662306a36Sopenharmony_ci}; 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_cistatic const struct attribute_group snd_ctl_led_dev_attr_group = { 45962306a36Sopenharmony_ci .attrs = snd_ctl_led_dev_attrs, 46062306a36Sopenharmony_ci}; 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_cistatic const struct attribute_group *snd_ctl_led_dev_attr_groups[] = { 46362306a36Sopenharmony_ci &snd_ctl_led_dev_attr_group, 46462306a36Sopenharmony_ci NULL, 46562306a36Sopenharmony_ci}; 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_cistatic char *find_eos(char *s) 46862306a36Sopenharmony_ci{ 46962306a36Sopenharmony_ci while (*s && *s != ',') 47062306a36Sopenharmony_ci s++; 47162306a36Sopenharmony_ci if (*s) 47262306a36Sopenharmony_ci s++; 47362306a36Sopenharmony_ci return s; 47462306a36Sopenharmony_ci} 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_cistatic char *parse_uint(char *s, unsigned int *val) 47762306a36Sopenharmony_ci{ 47862306a36Sopenharmony_ci unsigned long long res; 47962306a36Sopenharmony_ci if (kstrtoull(s, 10, &res)) 48062306a36Sopenharmony_ci res = 0; 48162306a36Sopenharmony_ci *val = res; 48262306a36Sopenharmony_ci return find_eos(s); 48362306a36Sopenharmony_ci} 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_cistatic char *parse_string(char *s, char *val, size_t val_size) 48662306a36Sopenharmony_ci{ 48762306a36Sopenharmony_ci if (*s == '"' || *s == '\'') { 48862306a36Sopenharmony_ci char c = *s; 48962306a36Sopenharmony_ci s++; 49062306a36Sopenharmony_ci while (*s && *s != c) { 49162306a36Sopenharmony_ci if (val_size > 1) { 49262306a36Sopenharmony_ci *val++ = *s; 49362306a36Sopenharmony_ci val_size--; 49462306a36Sopenharmony_ci } 49562306a36Sopenharmony_ci s++; 49662306a36Sopenharmony_ci } 49762306a36Sopenharmony_ci } else { 49862306a36Sopenharmony_ci while (*s && *s != ',') { 49962306a36Sopenharmony_ci if (val_size > 1) { 50062306a36Sopenharmony_ci *val++ = *s; 50162306a36Sopenharmony_ci val_size--; 50262306a36Sopenharmony_ci } 50362306a36Sopenharmony_ci s++; 50462306a36Sopenharmony_ci } 50562306a36Sopenharmony_ci } 50662306a36Sopenharmony_ci *val = '\0'; 50762306a36Sopenharmony_ci if (*s) 50862306a36Sopenharmony_ci s++; 50962306a36Sopenharmony_ci return s; 51062306a36Sopenharmony_ci} 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_cistatic char *parse_iface(char *s, snd_ctl_elem_iface_t *val) 51362306a36Sopenharmony_ci{ 51462306a36Sopenharmony_ci if (!strncasecmp(s, "card", 4)) 51562306a36Sopenharmony_ci *val = SNDRV_CTL_ELEM_IFACE_CARD; 51662306a36Sopenharmony_ci else if (!strncasecmp(s, "mixer", 5)) 51762306a36Sopenharmony_ci *val = SNDRV_CTL_ELEM_IFACE_MIXER; 51862306a36Sopenharmony_ci return find_eos(s); 51962306a36Sopenharmony_ci} 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ci/* 52262306a36Sopenharmony_ci * These types of input strings are accepted: 52362306a36Sopenharmony_ci * 52462306a36Sopenharmony_ci * unsigned integer - numid (equivaled to numid=UINT) 52562306a36Sopenharmony_ci * string - basic mixer name (equivalent to iface=MIXER,name=STR) 52662306a36Sopenharmony_ci * numid=UINT 52762306a36Sopenharmony_ci * [iface=MIXER,][device=UINT,][subdevice=UINT,]name=STR[,index=UINT] 52862306a36Sopenharmony_ci */ 52962306a36Sopenharmony_cistatic ssize_t set_led_id(struct snd_ctl_led_card *led_card, const char *buf, size_t count, 53062306a36Sopenharmony_ci bool attach) 53162306a36Sopenharmony_ci{ 53262306a36Sopenharmony_ci char buf2[256], *s, *os; 53362306a36Sopenharmony_ci struct snd_ctl_elem_id id; 53462306a36Sopenharmony_ci int err; 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_ci if (strscpy(buf2, buf, sizeof(buf2)) < 0) 53762306a36Sopenharmony_ci return -E2BIG; 53862306a36Sopenharmony_ci memset(&id, 0, sizeof(id)); 53962306a36Sopenharmony_ci id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; 54062306a36Sopenharmony_ci s = buf2; 54162306a36Sopenharmony_ci while (*s) { 54262306a36Sopenharmony_ci os = s; 54362306a36Sopenharmony_ci if (!strncasecmp(s, "numid=", 6)) { 54462306a36Sopenharmony_ci s = parse_uint(s + 6, &id.numid); 54562306a36Sopenharmony_ci } else if (!strncasecmp(s, "iface=", 6)) { 54662306a36Sopenharmony_ci s = parse_iface(s + 6, &id.iface); 54762306a36Sopenharmony_ci } else if (!strncasecmp(s, "device=", 7)) { 54862306a36Sopenharmony_ci s = parse_uint(s + 7, &id.device); 54962306a36Sopenharmony_ci } else if (!strncasecmp(s, "subdevice=", 10)) { 55062306a36Sopenharmony_ci s = parse_uint(s + 10, &id.subdevice); 55162306a36Sopenharmony_ci } else if (!strncasecmp(s, "name=", 5)) { 55262306a36Sopenharmony_ci s = parse_string(s + 5, id.name, sizeof(id.name)); 55362306a36Sopenharmony_ci } else if (!strncasecmp(s, "index=", 6)) { 55462306a36Sopenharmony_ci s = parse_uint(s + 6, &id.index); 55562306a36Sopenharmony_ci } else if (s == buf2) { 55662306a36Sopenharmony_ci while (*s) { 55762306a36Sopenharmony_ci if (*s < '0' || *s > '9') 55862306a36Sopenharmony_ci break; 55962306a36Sopenharmony_ci s++; 56062306a36Sopenharmony_ci } 56162306a36Sopenharmony_ci if (*s == '\0') 56262306a36Sopenharmony_ci parse_uint(buf2, &id.numid); 56362306a36Sopenharmony_ci else { 56462306a36Sopenharmony_ci for (; *s >= ' '; s++); 56562306a36Sopenharmony_ci *s = '\0'; 56662306a36Sopenharmony_ci strscpy(id.name, buf2, sizeof(id.name)); 56762306a36Sopenharmony_ci } 56862306a36Sopenharmony_ci break; 56962306a36Sopenharmony_ci } 57062306a36Sopenharmony_ci if (*s == ',') 57162306a36Sopenharmony_ci s++; 57262306a36Sopenharmony_ci if (s == os) 57362306a36Sopenharmony_ci break; 57462306a36Sopenharmony_ci } 57562306a36Sopenharmony_ci 57662306a36Sopenharmony_ci err = snd_ctl_led_set_id(led_card->number, &id, led_card->led->group, attach); 57762306a36Sopenharmony_ci if (err < 0) 57862306a36Sopenharmony_ci return err; 57962306a36Sopenharmony_ci 58062306a36Sopenharmony_ci return count; 58162306a36Sopenharmony_ci} 58262306a36Sopenharmony_ci 58362306a36Sopenharmony_cistatic ssize_t attach_store(struct device *dev, 58462306a36Sopenharmony_ci struct device_attribute *attr, 58562306a36Sopenharmony_ci const char *buf, size_t count) 58662306a36Sopenharmony_ci{ 58762306a36Sopenharmony_ci struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); 58862306a36Sopenharmony_ci return set_led_id(led_card, buf, count, true); 58962306a36Sopenharmony_ci} 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_cistatic ssize_t detach_store(struct device *dev, 59262306a36Sopenharmony_ci struct device_attribute *attr, 59362306a36Sopenharmony_ci const char *buf, size_t count) 59462306a36Sopenharmony_ci{ 59562306a36Sopenharmony_ci struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); 59662306a36Sopenharmony_ci return set_led_id(led_card, buf, count, false); 59762306a36Sopenharmony_ci} 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_cistatic ssize_t reset_store(struct device *dev, 60062306a36Sopenharmony_ci struct device_attribute *attr, 60162306a36Sopenharmony_ci const char *buf, size_t count) 60262306a36Sopenharmony_ci{ 60362306a36Sopenharmony_ci struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); 60462306a36Sopenharmony_ci int err; 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_ci if (count > 0 && buf[0] == '1') { 60762306a36Sopenharmony_ci err = snd_ctl_led_reset(led_card->number, led_card->led->group); 60862306a36Sopenharmony_ci if (err < 0) 60962306a36Sopenharmony_ci return err; 61062306a36Sopenharmony_ci } 61162306a36Sopenharmony_ci return count; 61262306a36Sopenharmony_ci} 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_cistatic ssize_t list_show(struct device *dev, 61562306a36Sopenharmony_ci struct device_attribute *attr, char *buf) 61662306a36Sopenharmony_ci{ 61762306a36Sopenharmony_ci struct snd_ctl_led_card *led_card = container_of(dev, struct snd_ctl_led_card, dev); 61862306a36Sopenharmony_ci struct snd_card *card; 61962306a36Sopenharmony_ci struct snd_ctl_led_ctl *lctl; 62062306a36Sopenharmony_ci size_t l = 0; 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_ci card = snd_card_ref(led_card->number); 62362306a36Sopenharmony_ci if (!card) 62462306a36Sopenharmony_ci return -ENXIO; 62562306a36Sopenharmony_ci down_read(&card->controls_rwsem); 62662306a36Sopenharmony_ci mutex_lock(&snd_ctl_led_mutex); 62762306a36Sopenharmony_ci if (snd_ctl_led_card_valid[led_card->number]) { 62862306a36Sopenharmony_ci list_for_each_entry(lctl, &led_card->led->controls, list) { 62962306a36Sopenharmony_ci if (lctl->card != card) 63062306a36Sopenharmony_ci continue; 63162306a36Sopenharmony_ci if (l) 63262306a36Sopenharmony_ci l += sysfs_emit_at(buf, l, " "); 63362306a36Sopenharmony_ci l += sysfs_emit_at(buf, l, "%u", 63462306a36Sopenharmony_ci lctl->kctl->id.numid + lctl->index_offset); 63562306a36Sopenharmony_ci } 63662306a36Sopenharmony_ci } 63762306a36Sopenharmony_ci mutex_unlock(&snd_ctl_led_mutex); 63862306a36Sopenharmony_ci up_read(&card->controls_rwsem); 63962306a36Sopenharmony_ci snd_card_unref(card); 64062306a36Sopenharmony_ci return l; 64162306a36Sopenharmony_ci} 64262306a36Sopenharmony_ci 64362306a36Sopenharmony_cistatic DEVICE_ATTR_WO(attach); 64462306a36Sopenharmony_cistatic DEVICE_ATTR_WO(detach); 64562306a36Sopenharmony_cistatic DEVICE_ATTR_WO(reset); 64662306a36Sopenharmony_cistatic DEVICE_ATTR_RO(list); 64762306a36Sopenharmony_ci 64862306a36Sopenharmony_cistatic struct attribute *snd_ctl_led_card_attrs[] = { 64962306a36Sopenharmony_ci &dev_attr_attach.attr, 65062306a36Sopenharmony_ci &dev_attr_detach.attr, 65162306a36Sopenharmony_ci &dev_attr_reset.attr, 65262306a36Sopenharmony_ci &dev_attr_list.attr, 65362306a36Sopenharmony_ci NULL, 65462306a36Sopenharmony_ci}; 65562306a36Sopenharmony_ci 65662306a36Sopenharmony_cistatic const struct attribute_group snd_ctl_led_card_attr_group = { 65762306a36Sopenharmony_ci .attrs = snd_ctl_led_card_attrs, 65862306a36Sopenharmony_ci}; 65962306a36Sopenharmony_ci 66062306a36Sopenharmony_cistatic const struct attribute_group *snd_ctl_led_card_attr_groups[] = { 66162306a36Sopenharmony_ci &snd_ctl_led_card_attr_group, 66262306a36Sopenharmony_ci NULL, 66362306a36Sopenharmony_ci}; 66462306a36Sopenharmony_ci 66562306a36Sopenharmony_cistatic struct device snd_ctl_led_dev; 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_cistatic void snd_ctl_led_sysfs_add(struct snd_card *card) 66862306a36Sopenharmony_ci{ 66962306a36Sopenharmony_ci unsigned int group; 67062306a36Sopenharmony_ci struct snd_ctl_led_card *led_card; 67162306a36Sopenharmony_ci struct snd_ctl_led *led; 67262306a36Sopenharmony_ci char link_name[32]; 67362306a36Sopenharmony_ci 67462306a36Sopenharmony_ci for (group = 0; group < MAX_LED; group++) { 67562306a36Sopenharmony_ci led = &snd_ctl_leds[group]; 67662306a36Sopenharmony_ci led_card = kzalloc(sizeof(*led_card), GFP_KERNEL); 67762306a36Sopenharmony_ci if (!led_card) 67862306a36Sopenharmony_ci goto cerr2; 67962306a36Sopenharmony_ci led_card->number = card->number; 68062306a36Sopenharmony_ci led_card->led = led; 68162306a36Sopenharmony_ci device_initialize(&led_card->dev); 68262306a36Sopenharmony_ci led_card->dev.release = snd_ctl_led_card_release; 68362306a36Sopenharmony_ci if (dev_set_name(&led_card->dev, "card%d", card->number) < 0) 68462306a36Sopenharmony_ci goto cerr; 68562306a36Sopenharmony_ci led_card->dev.parent = &led->dev; 68662306a36Sopenharmony_ci led_card->dev.groups = snd_ctl_led_card_attr_groups; 68762306a36Sopenharmony_ci if (device_add(&led_card->dev)) 68862306a36Sopenharmony_ci goto cerr; 68962306a36Sopenharmony_ci led->cards[card->number] = led_card; 69062306a36Sopenharmony_ci snprintf(link_name, sizeof(link_name), "led-%s", led->name); 69162306a36Sopenharmony_ci WARN(sysfs_create_link(&card->ctl_dev->kobj, &led_card->dev.kobj, link_name), 69262306a36Sopenharmony_ci "can't create symlink to controlC%i device\n", card->number); 69362306a36Sopenharmony_ci WARN(sysfs_create_link(&led_card->dev.kobj, &card->card_dev.kobj, "card"), 69462306a36Sopenharmony_ci "can't create symlink to card%i\n", card->number); 69562306a36Sopenharmony_ci 69662306a36Sopenharmony_ci continue; 69762306a36Sopenharmony_cicerr: 69862306a36Sopenharmony_ci put_device(&led_card->dev); 69962306a36Sopenharmony_cicerr2: 70062306a36Sopenharmony_ci printk(KERN_ERR "snd_ctl_led: unable to add card%d", card->number); 70162306a36Sopenharmony_ci } 70262306a36Sopenharmony_ci} 70362306a36Sopenharmony_ci 70462306a36Sopenharmony_cistatic void snd_ctl_led_sysfs_remove(struct snd_card *card) 70562306a36Sopenharmony_ci{ 70662306a36Sopenharmony_ci unsigned int group; 70762306a36Sopenharmony_ci struct snd_ctl_led_card *led_card; 70862306a36Sopenharmony_ci struct snd_ctl_led *led; 70962306a36Sopenharmony_ci char link_name[32]; 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_ci for (group = 0; group < MAX_LED; group++) { 71262306a36Sopenharmony_ci led = &snd_ctl_leds[group]; 71362306a36Sopenharmony_ci led_card = led->cards[card->number]; 71462306a36Sopenharmony_ci if (!led_card) 71562306a36Sopenharmony_ci continue; 71662306a36Sopenharmony_ci snprintf(link_name, sizeof(link_name), "led-%s", led->name); 71762306a36Sopenharmony_ci sysfs_remove_link(&card->ctl_dev->kobj, link_name); 71862306a36Sopenharmony_ci sysfs_remove_link(&led_card->dev.kobj, "card"); 71962306a36Sopenharmony_ci device_unregister(&led_card->dev); 72062306a36Sopenharmony_ci led->cards[card->number] = NULL; 72162306a36Sopenharmony_ci } 72262306a36Sopenharmony_ci} 72362306a36Sopenharmony_ci 72462306a36Sopenharmony_ci/* 72562306a36Sopenharmony_ci * Control layer registration 72662306a36Sopenharmony_ci */ 72762306a36Sopenharmony_cistatic struct snd_ctl_layer_ops snd_ctl_led_lops = { 72862306a36Sopenharmony_ci .module_name = SND_CTL_LAYER_MODULE_LED, 72962306a36Sopenharmony_ci .lregister = snd_ctl_led_register, 73062306a36Sopenharmony_ci .ldisconnect = snd_ctl_led_disconnect, 73162306a36Sopenharmony_ci .lnotify = snd_ctl_led_notify, 73262306a36Sopenharmony_ci}; 73362306a36Sopenharmony_ci 73462306a36Sopenharmony_cistatic int __init snd_ctl_led_init(void) 73562306a36Sopenharmony_ci{ 73662306a36Sopenharmony_ci struct snd_ctl_led *led; 73762306a36Sopenharmony_ci unsigned int group; 73862306a36Sopenharmony_ci 73962306a36Sopenharmony_ci device_initialize(&snd_ctl_led_dev); 74062306a36Sopenharmony_ci snd_ctl_led_dev.class = &sound_class; 74162306a36Sopenharmony_ci snd_ctl_led_dev.release = snd_ctl_led_dev_release; 74262306a36Sopenharmony_ci dev_set_name(&snd_ctl_led_dev, "ctl-led"); 74362306a36Sopenharmony_ci if (device_add(&snd_ctl_led_dev)) { 74462306a36Sopenharmony_ci put_device(&snd_ctl_led_dev); 74562306a36Sopenharmony_ci return -ENOMEM; 74662306a36Sopenharmony_ci } 74762306a36Sopenharmony_ci for (group = 0; group < MAX_LED; group++) { 74862306a36Sopenharmony_ci led = &snd_ctl_leds[group]; 74962306a36Sopenharmony_ci INIT_LIST_HEAD(&led->controls); 75062306a36Sopenharmony_ci device_initialize(&led->dev); 75162306a36Sopenharmony_ci led->dev.parent = &snd_ctl_led_dev; 75262306a36Sopenharmony_ci led->dev.release = snd_ctl_led_release; 75362306a36Sopenharmony_ci led->dev.groups = snd_ctl_led_dev_attr_groups; 75462306a36Sopenharmony_ci dev_set_name(&led->dev, led->name); 75562306a36Sopenharmony_ci if (device_add(&led->dev)) { 75662306a36Sopenharmony_ci put_device(&led->dev); 75762306a36Sopenharmony_ci for (; group > 0; group--) { 75862306a36Sopenharmony_ci led = &snd_ctl_leds[group - 1]; 75962306a36Sopenharmony_ci device_unregister(&led->dev); 76062306a36Sopenharmony_ci } 76162306a36Sopenharmony_ci device_unregister(&snd_ctl_led_dev); 76262306a36Sopenharmony_ci return -ENOMEM; 76362306a36Sopenharmony_ci } 76462306a36Sopenharmony_ci } 76562306a36Sopenharmony_ci snd_ctl_register_layer(&snd_ctl_led_lops); 76662306a36Sopenharmony_ci return 0; 76762306a36Sopenharmony_ci} 76862306a36Sopenharmony_ci 76962306a36Sopenharmony_cistatic void __exit snd_ctl_led_exit(void) 77062306a36Sopenharmony_ci{ 77162306a36Sopenharmony_ci struct snd_ctl_led *led; 77262306a36Sopenharmony_ci struct snd_card *card; 77362306a36Sopenharmony_ci unsigned int group, card_number; 77462306a36Sopenharmony_ci 77562306a36Sopenharmony_ci snd_ctl_disconnect_layer(&snd_ctl_led_lops); 77662306a36Sopenharmony_ci for (card_number = 0; card_number < SNDRV_CARDS; card_number++) { 77762306a36Sopenharmony_ci if (!snd_ctl_led_card_valid[card_number]) 77862306a36Sopenharmony_ci continue; 77962306a36Sopenharmony_ci card = snd_card_ref(card_number); 78062306a36Sopenharmony_ci if (card) { 78162306a36Sopenharmony_ci snd_ctl_led_sysfs_remove(card); 78262306a36Sopenharmony_ci snd_card_unref(card); 78362306a36Sopenharmony_ci } 78462306a36Sopenharmony_ci } 78562306a36Sopenharmony_ci for (group = 0; group < MAX_LED; group++) { 78662306a36Sopenharmony_ci led = &snd_ctl_leds[group]; 78762306a36Sopenharmony_ci device_unregister(&led->dev); 78862306a36Sopenharmony_ci } 78962306a36Sopenharmony_ci device_unregister(&snd_ctl_led_dev); 79062306a36Sopenharmony_ci snd_ctl_led_clean(NULL); 79162306a36Sopenharmony_ci} 79262306a36Sopenharmony_ci 79362306a36Sopenharmony_cimodule_init(snd_ctl_led_init) 79462306a36Sopenharmony_cimodule_exit(snd_ctl_led_exit) 795