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