All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jaroslav Kysela <perex@perex.cz>
To: ALSA development <alsa-devel@alsa-project.org>
Cc: Takashi Iwai <tiwai@suse.de>, Hans de Goede <hdegoede@redhat.com>
Subject: [PATCH v4 5/6] ALSA: control - add sysfs support to the LED trigger module
Date: Wed, 17 Mar 2021 18:29:44 +0100	[thread overview]
Message-ID: <20210317172945.842280-6-perex@perex.cz> (raw)
In-Reply-To: <20210317172945.842280-1-perex@perex.cz>

Create SYSFS/devices/virtual/sound/ctl-led tree
(with SYSFS/class/sound/ctl-led symlink).

  speaker/
    +-- mode
    +-- brightness
  mic/
    +-- mode
    +-- brightness

Copy the idea from the HDA driver and allow to set the audio
LEDs based on the various modes:

- follow mute
- follow moute (inverted to follow mute)
- off
- on

Also, the actual LED state is exposed.

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
---
 sound/core/control_led.c | 192 +++++++++++++++++++++++++++++++++------
 1 file changed, 163 insertions(+), 29 deletions(-)

diff --git a/sound/core/control_led.c b/sound/core/control_led.c
index 3e2c3c485c5c..dfa51d8461e1 100644
--- a/sound/core/control_led.c
+++ b/sound/core/control_led.c
@@ -17,7 +17,23 @@ 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)
 
+enum snd_ctl_led_mode {
+	 MODE_FOLLOW_MUTE = 0,
+	 MODE_FOLLOW_ROUTE,
+	 MODE_OFF,
+	 MODE_ON,
+};
+
 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_ctl {
 	struct list_head list;
 	struct snd_card *card;
 	unsigned int access;
@@ -26,8 +42,21 @@ struct snd_ctl_led {
 };
 
 static DEFINE_MUTEX(snd_ctl_led_mutex);
-static struct list_head snd_ctl_led_controls[MAX_LED];
 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,
+	},
+};
 
 #define UPDATE_ROUTE(route, cb) \
 	do { \
@@ -47,15 +76,15 @@ static inline unsigned int group_to_access(unsigned int group)
 	return (group + 1) << SNDRV_CTL_ELEM_ACCESS_LED_SHIFT;
 }
 
-static struct list_head *snd_ctl_led_controls_by_access(unsigned int access)
+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_led_controls[group];
+	return &snd_ctl_leds[group];
 }
 
-static int snd_ctl_led_get(struct snd_ctl_led *lctl)
+static int snd_ctl_led_get(struct snd_ctl_led_ctl *lctl)
 {
 	struct snd_kcontrol *kctl = lctl->kctl;
 	struct snd_ctl_elem_info info;
@@ -91,22 +120,14 @@ static int snd_ctl_led_get(struct snd_ctl_led *lctl)
 static void snd_ctl_led_set_state(struct snd_card *card, unsigned int access,
 				  struct snd_kcontrol *kctl, unsigned int ioff)
 {
-	struct list_head *controls;
-	struct snd_ctl_led *lctl;
-	enum led_audio led_trigger_type;
+	struct snd_ctl_led *led;
+	struct snd_ctl_led_ctl *lctl;
 	int route;
 	bool found;
 
-	controls = snd_ctl_led_controls_by_access(access);
-	if (!controls)
+	led = snd_ctl_led_get_by_access(access);
+	if (!led)
 		return;
-	if (access == SNDRV_CTL_ELEM_ACCESS_SPK_LED) {
-		led_trigger_type = LED_AUDIO_MUTE;
-	} else if (access == SNDRV_CTL_ELEM_ACCESS_MIC_LED) {
-		led_trigger_type = LED_AUDIO_MICMUTE;
-	} else {
-		return;
-	}
 	route = -1;
 	found = false;
 	mutex_lock(&snd_ctl_led_mutex);
@@ -115,7 +136,7 @@ static void snd_ctl_led_set_state(struct snd_card *card, unsigned int access,
 		mutex_unlock(&snd_ctl_led_mutex);
 		return;
 	}
-	list_for_each_entry(lctl, controls, list) {
+	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));
@@ -127,23 +148,29 @@ static void snd_ctl_led_set_state(struct snd_card *card, unsigned int access,
 			lctl->access = access;
 			lctl->kctl = kctl;
 			lctl->index_offset = ioff;
-			list_add(&lctl->list, controls);
+			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);
+		ledtrig_audio_set(led->trigger_type, route ? LED_OFF : LED_ON);
 }
 
-static struct snd_ctl_led *snd_ctl_led_find(struct snd_kcontrol *kctl, unsigned int ioff)
+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 *lctl;
+	struct snd_ctl_led_ctl *lctl;
 	unsigned int group;
 
 	for (group = 0; group < MAX_LED; group++) {
-		controls = &snd_ctl_led_controls[group];
+		controls = &snd_ctl_leds[group].controls;
 		list_for_each_entry(lctl, controls, list)
 			if (lctl->kctl == kctl && lctl->index_offset == ioff)
 				return lctl;
@@ -154,7 +181,7 @@ static struct snd_ctl_led *snd_ctl_led_find(struct snd_kcontrol *kctl, unsigned
 static unsigned int snd_ctl_led_remove(struct snd_kcontrol *kctl, unsigned int ioff,
 				       unsigned int access)
 {
-	struct snd_ctl_led *lctl;
+	struct snd_ctl_led_ctl *lctl;
 	unsigned int ret = 0;
 
 	mutex_lock(&snd_ctl_led_mutex);
@@ -206,13 +233,13 @@ static void snd_ctl_led_refresh(void)
 static void snd_ctl_led_clean(struct snd_card *card)
 {
 	unsigned int group;
-	struct list_head *controls;
-	struct snd_ctl_led *lctl;
+	struct snd_ctl_led *led;
+	struct snd_ctl_led_ctl *lctl;
 
 	for (group = 0; group < MAX_LED; group++) {
-		controls = &snd_ctl_led_controls[group];
+		led = &snd_ctl_leds[group];
 repeat:
-		list_for_each_entry(lctl, controls, list)
+		list_for_each_entry(lctl, &led->controls, list)
 			if (!card || lctl->card == card) {
 				list_del(&lctl->list);
 				kfree(lctl);
@@ -248,6 +275,82 @@ static void snd_ctl_led_disconnect(struct snd_card *card)
 	snd_ctl_led_refresh();
 }
 
+/*
+ * sysfs
+ */
+
+static ssize_t show_mode(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;
+
+	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 sprintf(buf, "%s\n", str);
+}
+
+static ssize_t store_mode(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) + 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 show_brightness(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev);
+
+	return sprintf(buf, "%u\n", ledtrig_audio_get(led->trigger_type));
+}
+
+static DEVICE_ATTR(mode, 0644, show_mode, store_mode);
+static DEVICE_ATTR(brightness, 0444, show_brightness, NULL);
+
+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 struct device snd_ctl_led_dev;
+
 /*
  * Control layer registration
  */
@@ -260,16 +363,47 @@ static struct snd_ctl_layer_ops snd_ctl_led_lops = {
 
 static int __init snd_ctl_led_init(void)
 {
+	struct snd_ctl_led *led;
 	unsigned int group;
 
-	for (group = 0; group < MAX_LED; group++)
-		INIT_LIST_HEAD(&snd_ctl_led_controls[group]);
+	device_initialize(&snd_ctl_led_dev);
+	snd_ctl_led_dev.class = sound_class;
+	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.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];
+				device_del(&led->dev);
+			}
+			device_del(&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;
+	unsigned int group;
+
+	for (group = 0; group < MAX_LED; group++) {
+		led = &snd_ctl_leds[group];
+		device_del(&led->dev);
+	}
+	device_del(&snd_ctl_led_dev);
 	snd_ctl_disconnect_layer(&snd_ctl_led_lops);
 	snd_ctl_led_clean(NULL);
 }
-- 
2.29.2

  parent reply	other threads:[~2021-03-17 17:32 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-03-17 17:29 [PATCH v4 0/6] ALSA: control - add generic LED API Jaroslav Kysela
2021-03-17 17:29 ` [PATCH v4 1/6] ALSA: control - introduce snd_ctl_notify_one() helper Jaroslav Kysela
2021-03-17 17:29 ` [PATCH v4 2/6] ALSA: control - add layer registration routines Jaroslav Kysela
2021-03-17 17:29 ` [PATCH v4 3/6] ALSA: control - add generic LED trigger module as the new control layer Jaroslav Kysela
2021-03-17 17:29 ` [PATCH v4 4/6] ALSA: HDA - remove the custom implementation for the audio LED trigger Jaroslav Kysela
2021-03-17 17:29 ` Jaroslav Kysela [this message]
2021-03-17 17:29 ` [PATCH v4 6/6] ALSA: led control - add sysfs kcontrol LED marking layer Jaroslav Kysela
2021-03-19 16:34   ` Hans de Goede
2021-03-19 17:22     ` Takashi Iwai
2021-03-19 17:58       ` Jaroslav Kysela
2021-03-19 22:08       ` Hans de Goede
2021-03-20  7:41         ` Takashi Iwai
2021-03-20  9:17           ` Hans de Goede
2021-03-20  9:48             ` Takashi Iwai
2021-03-22 14:16               ` Jaroslav Kysela
2021-03-23  9:38                 ` Takashi Iwai
2021-03-23  9:49                   ` Takashi Iwai
2021-03-23 10:31                     ` Jaroslav Kysela
2021-03-23 10:42                       ` Hans de Goede
2021-03-23 11:03                         ` Takashi Iwai
2021-03-23 10:50                       ` Takashi Iwai
2021-03-23 11:13                         ` Jaroslav Kysela
2021-03-23 11:34                           ` Takashi Iwai
2021-03-23 12:22                             ` Jaroslav Kysela
2021-03-23 21:39                 ` Curtis Malainey
2021-03-23 22:49                   ` Dylan Reid
2021-03-26  8:07                     ` Takashi Iwai
2021-03-19 18:11     ` Jaroslav Kysela
2021-03-19 22:13       ` Hans de Goede
2021-03-30 15:49 ` [PATCH v4 0/6] ALSA: control - add generic LED API Takashi Iwai
2021-03-17 20:42 [PATCH v4 5/6] ALSA: control - add sysfs support to the LED trigger module kernel test robot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210317172945.842280-6-perex@perex.cz \
    --to=perex@perex.cz \
    --cc=alsa-devel@alsa-project.org \
    --cc=hdegoede@redhat.com \
    --cc=tiwai@suse.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.