Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

leds: class: Add new optional brightness_hw_changed attribute

Some LEDs may have their brightness level changed autonomously
(outside of kernel control) by hardware / firmware. This commit
adds support for an optional brightness_hw_changed attribute to
signal such changes to userspace (if a driver can detect them):

What: /sys/class/leds/<led>/brightness_hw_changed
Date: January 2017
KernelVersion: 4.11
Description:
Last hardware set brightness level for this LED. Some LEDs
may be changed autonomously by hardware/firmware. Only LEDs
where this happens and the driver can detect this, will
have this file.

This file supports poll() to detect when the hardware
changes the brightness.

Reading this file will return the last brightness level set
by the hardware, this may be different from the current
brightness.

Drivers which want to support this, simply add LED_BRIGHT_HW_CHANGED to
their flags field and call led_classdev_notify_brightness_hw_changed()
with the hardware set brightness when they detect a hardware / firmware
triggered brightness change.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Acked-by: Pavel Machek <pavel@ucw.cz>
Signed-off-by: Jacek Anaszewski <jacek.anaszewski@gmail.com>

authored by

Hans de Goede and committed by
Jacek Anaszewski
0cb8eb30 cbe99c53

+132
+17
Documentation/ABI/testing/sysfs-class-led
··· 23 23 If the LED does not support different brightness levels, this 24 24 should be 1. 25 25 26 + What: /sys/class/leds/<led>/brightness_hw_changed 27 + Date: January 2017 28 + KernelVersion: 4.11 29 + Description: 30 + Last hardware set brightness level for this LED. Some LEDs 31 + may be changed autonomously by hardware/firmware. Only LEDs 32 + where this happens and the driver can detect this, will have 33 + this file. 34 + 35 + This file supports poll() to detect when the hardware changes 36 + the brightness. 37 + 38 + Reading this file will return the last brightness level set 39 + by the hardware, this may be different from the current 40 + brightness. Reading this file when no hw brightness change 41 + event has happened will return an ENODATA error. 42 + 26 43 What: /sys/class/leds/<led>/trigger 27 44 Date: March 2006 28 45 KernelVersion: 2.6.17
+15
Documentation/leds/leds-class.txt
··· 65 65 blinking, returns -EBUSY if software blink fallback is enabled. 66 66 67 67 68 + LED registration API 69 + ==================== 70 + 71 + A driver wanting to register a LED classdev for use by other drivers / 72 + userspace needs to allocate and fill a led_classdev struct and then call 73 + [devm_]led_classdev_register. If the non devm version is used the driver 74 + must call led_classdev_unregister from its remove function before 75 + free-ing the led_classdev struct. 76 + 77 + If the driver can detect hardware initiated brightness changes and thus 78 + wants to have a brightness_hw_changed attribute then the LED_BRIGHT_HW_CHANGED 79 + flag must be set in flags before registering. Calling 80 + led_classdev_notify_brightness_hw_changed on a classdev not registered with 81 + the LED_BRIGHT_HW_CHANGED flag is a bug and will trigger a WARN_ON. 82 + 68 83 Hardware accelerated blink of LEDs 69 84 ================================== 70 85
+9
drivers/leds/Kconfig
··· 29 29 for the flash related features of a LED device. It can be built 30 30 as a module. 31 31 32 + config LEDS_BRIGHTNESS_HW_CHANGED 33 + bool "LED Class brightness_hw_changed attribute support" 34 + depends on LEDS_CLASS 35 + help 36 + This option enables support for the brightness_hw_changed attribute 37 + for led sysfs class devices under /sys/class/leds. 38 + 39 + See Documentation/ABI/testing/sysfs-class-led for details. 40 + 32 41 comment "LED drivers" 33 42 34 43 config LEDS_88PM860X
+76
drivers/leds/led-class.c
··· 103 103 NULL, 104 104 }; 105 105 106 + #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED 107 + static ssize_t brightness_hw_changed_show(struct device *dev, 108 + struct device_attribute *attr, char *buf) 109 + { 110 + struct led_classdev *led_cdev = dev_get_drvdata(dev); 111 + 112 + if (led_cdev->brightness_hw_changed == -1) 113 + return -ENODATA; 114 + 115 + return sprintf(buf, "%u\n", led_cdev->brightness_hw_changed); 116 + } 117 + 118 + static DEVICE_ATTR_RO(brightness_hw_changed); 119 + 120 + static int led_add_brightness_hw_changed(struct led_classdev *led_cdev) 121 + { 122 + struct device *dev = led_cdev->dev; 123 + int ret; 124 + 125 + ret = device_create_file(dev, &dev_attr_brightness_hw_changed); 126 + if (ret) { 127 + dev_err(dev, "Error creating brightness_hw_changed\n"); 128 + return ret; 129 + } 130 + 131 + led_cdev->brightness_hw_changed_kn = 132 + sysfs_get_dirent(dev->kobj.sd, "brightness_hw_changed"); 133 + if (!led_cdev->brightness_hw_changed_kn) { 134 + dev_err(dev, "Error getting brightness_hw_changed kn\n"); 135 + device_remove_file(dev, &dev_attr_brightness_hw_changed); 136 + return -ENXIO; 137 + } 138 + 139 + return 0; 140 + } 141 + 142 + static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev) 143 + { 144 + sysfs_put(led_cdev->brightness_hw_changed_kn); 145 + device_remove_file(led_cdev->dev, &dev_attr_brightness_hw_changed); 146 + } 147 + 148 + void led_classdev_notify_brightness_hw_changed(struct led_classdev *led_cdev, 149 + enum led_brightness brightness) 150 + { 151 + if (WARN_ON(!led_cdev->brightness_hw_changed_kn)) 152 + return; 153 + 154 + led_cdev->brightness_hw_changed = brightness; 155 + sysfs_notify_dirent(led_cdev->brightness_hw_changed_kn); 156 + } 157 + EXPORT_SYMBOL_GPL(led_classdev_notify_brightness_hw_changed); 158 + #else 159 + static int led_add_brightness_hw_changed(struct led_classdev *led_cdev) 160 + { 161 + return 0; 162 + } 163 + static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev) 164 + { 165 + } 166 + #endif 167 + 106 168 /** 107 169 * led_classdev_suspend - suspend an led_classdev. 108 170 * @led_cdev: the led_classdev to suspend. ··· 266 204 dev_warn(parent, "Led %s renamed to %s due to name collision", 267 205 led_cdev->name, dev_name(led_cdev->dev)); 268 206 207 + if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) { 208 + ret = led_add_brightness_hw_changed(led_cdev); 209 + if (ret) { 210 + device_unregister(led_cdev->dev); 211 + return ret; 212 + } 213 + } 214 + 269 215 led_cdev->work_flags = 0; 270 216 #ifdef CONFIG_LEDS_TRIGGERS 271 217 init_rwsem(&led_cdev->trigger_lock); 218 + #endif 219 + #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED 220 + led_cdev->brightness_hw_changed = -1; 272 221 #endif 273 222 mutex_init(&led_cdev->led_access); 274 223 /* add to the list of leds */ ··· 328 255 led_set_brightness(led_cdev, LED_OFF); 329 256 330 257 flush_work(&led_cdev->set_brightness_work); 258 + 259 + if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) 260 + led_remove_brightness_hw_changed(led_cdev); 331 261 332 262 device_unregister(led_cdev->dev); 333 263
+15
include/linux/leds.h
··· 13 13 #define __LINUX_LEDS_H_INCLUDED 14 14 15 15 #include <linux/device.h> 16 + #include <linux/kernfs.h> 16 17 #include <linux/list.h> 17 18 #include <linux/mutex.h> 18 19 #include <linux/rwsem.h> ··· 48 47 #define LED_DEV_CAP_FLASH (1 << 18) 49 48 #define LED_HW_PLUGGABLE (1 << 19) 50 49 #define LED_PANIC_INDICATOR (1 << 20) 50 + #define LED_BRIGHT_HW_CHANGED (1 << 21) 51 51 52 52 /* set_brightness_work / blink_timer flags, atomic, private. */ 53 53 unsigned long work_flags; ··· 111 109 void *trigger_data; 112 110 /* true if activated - deactivate routine uses it to do cleanup */ 113 111 bool activated; 112 + #endif 113 + 114 + #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED 115 + int brightness_hw_changed; 116 + struct kernfs_node *brightness_hw_changed_kn; 114 117 #endif 115 118 116 119 /* Ensures consistent access to the LED Flash Class device */ ··· 428 421 { 429 422 return; 430 423 } 424 + #endif 425 + 426 + #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED 427 + extern void led_classdev_notify_brightness_hw_changed( 428 + struct led_classdev *led_cdev, enum led_brightness brightness); 429 + #else 430 + static inline void led_classdev_notify_brightness_hw_changed( 431 + struct led_classdev *led_cdev, enum led_brightness brightness) { } 431 432 #endif 432 433 433 434 #endif /* __LINUX_LEDS_H_INCLUDED */