aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/ABI/testing/sysfs-class-led17
-rw-r--r--Documentation/devicetree/bindings/leds/common.txt28
-rw-r--r--Documentation/leds/leds-class.txt15
-rw-r--r--drivers/leds/Kconfig9
-rw-r--r--drivers/leds/led-class.c76
-rw-r--r--drivers/leds/leds-ktd2692.c8
-rw-r--r--drivers/leds/trigger/ledtrig-heartbeat.c15
-rw-r--r--include/linux/leds.h16
-rw-r--r--tools/leds/Makefile4
-rw-r--r--tools/leds/led_hw_brightness_mon.c84
10 files changed, 252 insertions, 20 deletions
diff --git a/Documentation/ABI/testing/sysfs-class-led b/Documentation/ABI/testing/sysfs-class-led
index 491cdeedc195..5f67f7ab277b 100644
--- a/Documentation/ABI/testing/sysfs-class-led
+++ b/Documentation/ABI/testing/sysfs-class-led
@@ -23,6 +23,23 @@ Description:
23 If the LED does not support different brightness levels, this 23 If the LED does not support different brightness levels, this
24 should be 1. 24 should be 1.
25 25
26What: /sys/class/leds/<led>/brightness_hw_changed
27Date: January 2017
28KernelVersion: 4.11
29Description:
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
26What: /sys/class/leds/<led>/trigger 43What: /sys/class/leds/<led>/trigger
27Date: March 2006 44Date: March 2006
28KernelVersion: 2.6.17 45KernelVersion: 2.6.17
diff --git a/Documentation/devicetree/bindings/leds/common.txt b/Documentation/devicetree/bindings/leds/common.txt
index 696be5792625..24b656014089 100644
--- a/Documentation/devicetree/bindings/leds/common.txt
+++ b/Documentation/devicetree/bindings/leds/common.txt
@@ -61,16 +61,24 @@ property can be omitted.
61 61
62Examples: 62Examples:
63 63
64system-status { 64gpio-leds {
65 label = "Status"; 65 compatible = "gpio-leds";
66 linux,default-trigger = "heartbeat"; 66
67 ... 67 system-status {
68 label = "Status";
69 linux,default-trigger = "heartbeat";
70 gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>;
71 };
68}; 72};
69 73
70camera-flash { 74max77693-led {
71 label = "Flash"; 75 compatible = "maxim,max77693-led";
72 led-sources = <0>, <1>; 76
73 led-max-microamp = <50000>; 77 camera-flash {
74 flash-max-microamp = <320000>; 78 label = "Flash";
75 flash-max-timeout-us = <500000>; 79 led-sources = <0>, <1>;
80 led-max-microamp = <50000>;
81 flash-max-microamp = <320000>;
82 flash-max-timeout-us = <500000>;
83 };
76}; 84};
diff --git a/Documentation/leds/leds-class.txt b/Documentation/leds/leds-class.txt
index f1f7ec9f5cc5..836cb16d6f09 100644
--- a/Documentation/leds/leds-class.txt
+++ b/Documentation/leds/leds-class.txt
@@ -65,6 +65,21 @@ LED subsystem core exposes following API for setting brightness:
65 blinking, returns -EBUSY if software blink fallback is enabled. 65 blinking, returns -EBUSY if software blink fallback is enabled.
66 66
67 67
68LED registration API
69====================
70
71A driver wanting to register a LED classdev for use by other drivers /
72userspace 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
74must call led_classdev_unregister from its remove function before
75free-ing the led_classdev struct.
76
77If the driver can detect hardware initiated brightness changes and thus
78wants to have a brightness_hw_changed attribute then the LED_BRIGHT_HW_CHANGED
79flag must be set in flags before registering. Calling
80led_classdev_notify_brightness_hw_changed on a classdev not registered with
81the LED_BRIGHT_HW_CHANGED flag is a bug and will trigger a WARN_ON.
82
68Hardware accelerated blink of LEDs 83Hardware accelerated blink of LEDs
69================================== 84==================================
70 85
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index c621cbbb5768..275f467956ee 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -29,6 +29,15 @@ config LEDS_CLASS_FLASH
29 for the flash related features of a LED device. It can be built 29 for the flash related features of a LED device. It can be built
30 as a module. 30 as a module.
31 31
32config 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
32comment "LED drivers" 41comment "LED drivers"
33 42
34config LEDS_88PM860X 43config LEDS_88PM860X
diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
index 326ee6e925a2..f2b0a80a62b4 100644
--- a/drivers/leds/led-class.c
+++ b/drivers/leds/led-class.c
@@ -103,6 +103,68 @@ static const struct attribute_group *led_groups[] = {
103 NULL, 103 NULL,
104}; 104};
105 105
106#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
107static 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
118static DEVICE_ATTR_RO(brightness_hw_changed);
119
120static 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
142static 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
148void 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}
157EXPORT_SYMBOL_GPL(led_classdev_notify_brightness_hw_changed);
158#else
159static int led_add_brightness_hw_changed(struct led_classdev *led_cdev)
160{
161 return 0;
162}
163static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev)
164{
165}
166#endif
167
106/** 168/**
107 * led_classdev_suspend - suspend an led_classdev. 169 * led_classdev_suspend - suspend an led_classdev.
108 * @led_cdev: the led_classdev to suspend. 170 * @led_cdev: the led_classdev to suspend.
@@ -204,10 +266,21 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
204 dev_warn(parent, "Led %s renamed to %s due to name collision", 266 dev_warn(parent, "Led %s renamed to %s due to name collision",
205 led_cdev->name, dev_name(led_cdev->dev)); 267 led_cdev->name, dev_name(led_cdev->dev));
206 268
269 if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) {
270 ret = led_add_brightness_hw_changed(led_cdev);
271 if (ret) {
272 device_unregister(led_cdev->dev);
273 return ret;
274 }
275 }
276
207 led_cdev->work_flags = 0; 277 led_cdev->work_flags = 0;
208#ifdef CONFIG_LEDS_TRIGGERS 278#ifdef CONFIG_LEDS_TRIGGERS
209 init_rwsem(&led_cdev->trigger_lock); 279 init_rwsem(&led_cdev->trigger_lock);
210#endif 280#endif
281#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
282 led_cdev->brightness_hw_changed = -1;
283#endif
211 mutex_init(&led_cdev->led_access); 284 mutex_init(&led_cdev->led_access);
212 /* add to the list of leds */ 285 /* add to the list of leds */
213 down_write(&leds_list_lock); 286 down_write(&leds_list_lock);
@@ -256,6 +329,9 @@ void led_classdev_unregister(struct led_classdev *led_cdev)
256 329
257 flush_work(&led_cdev->set_brightness_work); 330 flush_work(&led_cdev->set_brightness_work);
258 331
332 if (led_cdev->flags & LED_BRIGHT_HW_CHANGED)
333 led_remove_brightness_hw_changed(led_cdev);
334
259 device_unregister(led_cdev->dev); 335 device_unregister(led_cdev->dev);
260 336
261 down_write(&leds_list_lock); 337 down_write(&leds_list_lock);
diff --git a/drivers/leds/leds-ktd2692.c b/drivers/leds/leds-ktd2692.c
index bf23ba191ad0..45296aaca9da 100644
--- a/drivers/leds/leds-ktd2692.c
+++ b/drivers/leds/leds-ktd2692.c
@@ -270,15 +270,15 @@ static int ktd2692_parse_dt(struct ktd2692_context *led, struct device *dev,
270 return -ENXIO; 270 return -ENXIO;
271 271
272 led->ctrl_gpio = devm_gpiod_get(dev, "ctrl", GPIOD_ASIS); 272 led->ctrl_gpio = devm_gpiod_get(dev, "ctrl", GPIOD_ASIS);
273 if (IS_ERR(led->ctrl_gpio)) { 273 ret = PTR_ERR_OR_ZERO(led->ctrl_gpio);
274 ret = PTR_ERR(led->ctrl_gpio); 274 if (ret) {
275 dev_err(dev, "cannot get ctrl-gpios %d\n", ret); 275 dev_err(dev, "cannot get ctrl-gpios %d\n", ret);
276 return ret; 276 return ret;
277 } 277 }
278 278
279 led->aux_gpio = devm_gpiod_get(dev, "aux", GPIOD_ASIS); 279 led->aux_gpio = devm_gpiod_get(dev, "aux", GPIOD_ASIS);
280 if (IS_ERR(led->aux_gpio)) { 280 ret = PTR_ERR_OR_ZERO(led->aux_gpio);
281 ret = PTR_ERR(led->aux_gpio); 281 if (ret) {
282 dev_err(dev, "cannot get aux-gpios %d\n", ret); 282 dev_err(dev, "cannot get aux-gpios %d\n", ret);
283 return ret; 283 return ret;
284 } 284 }
diff --git a/drivers/leds/trigger/ledtrig-heartbeat.c b/drivers/leds/trigger/ledtrig-heartbeat.c
index c9f386213e9e..e6f2f8b9f09a 100644
--- a/drivers/leds/trigger/ledtrig-heartbeat.c
+++ b/drivers/leds/trigger/ledtrig-heartbeat.c
@@ -43,6 +43,9 @@ static void led_heartbeat_function(unsigned long data)
43 return; 43 return;
44 } 44 }
45 45
46 if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE, &led_cdev->work_flags))
47 led_cdev->blink_brightness = led_cdev->new_blink_brightness;
48
46 /* acts like an actual heart beat -- ie thump-thump-pause... */ 49 /* acts like an actual heart beat -- ie thump-thump-pause... */
47 switch (heartbeat_data->phase) { 50 switch (heartbeat_data->phase) {
48 case 0: 51 case 0:
@@ -59,26 +62,26 @@ static void led_heartbeat_function(unsigned long data)
59 delay = msecs_to_jiffies(70); 62 delay = msecs_to_jiffies(70);
60 heartbeat_data->phase++; 63 heartbeat_data->phase++;
61 if (!heartbeat_data->invert) 64 if (!heartbeat_data->invert)
62 brightness = led_cdev->max_brightness; 65 brightness = led_cdev->blink_brightness;
63 break; 66 break;
64 case 1: 67 case 1:
65 delay = heartbeat_data->period / 4 - msecs_to_jiffies(70); 68 delay = heartbeat_data->period / 4 - msecs_to_jiffies(70);
66 heartbeat_data->phase++; 69 heartbeat_data->phase++;
67 if (heartbeat_data->invert) 70 if (heartbeat_data->invert)
68 brightness = led_cdev->max_brightness; 71 brightness = led_cdev->blink_brightness;
69 break; 72 break;
70 case 2: 73 case 2:
71 delay = msecs_to_jiffies(70); 74 delay = msecs_to_jiffies(70);
72 heartbeat_data->phase++; 75 heartbeat_data->phase++;
73 if (!heartbeat_data->invert) 76 if (!heartbeat_data->invert)
74 brightness = led_cdev->max_brightness; 77 brightness = led_cdev->blink_brightness;
75 break; 78 break;
76 default: 79 default:
77 delay = heartbeat_data->period - heartbeat_data->period / 4 - 80 delay = heartbeat_data->period - heartbeat_data->period / 4 -
78 msecs_to_jiffies(70); 81 msecs_to_jiffies(70);
79 heartbeat_data->phase = 0; 82 heartbeat_data->phase = 0;
80 if (heartbeat_data->invert) 83 if (heartbeat_data->invert)
81 brightness = led_cdev->max_brightness; 84 brightness = led_cdev->blink_brightness;
82 break; 85 break;
83 } 86 }
84 87
@@ -133,7 +136,10 @@ static void heartbeat_trig_activate(struct led_classdev *led_cdev)
133 setup_timer(&heartbeat_data->timer, 136 setup_timer(&heartbeat_data->timer,
134 led_heartbeat_function, (unsigned long) led_cdev); 137 led_heartbeat_function, (unsigned long) led_cdev);
135 heartbeat_data->phase = 0; 138 heartbeat_data->phase = 0;
139 if (!led_cdev->blink_brightness)
140 led_cdev->blink_brightness = led_cdev->max_brightness;
136 led_heartbeat_function(heartbeat_data->timer.data); 141 led_heartbeat_function(heartbeat_data->timer.data);
142 set_bit(LED_BLINK_SW, &led_cdev->work_flags);
137 led_cdev->activated = true; 143 led_cdev->activated = true;
138} 144}
139 145
@@ -145,6 +151,7 @@ static void heartbeat_trig_deactivate(struct led_classdev *led_cdev)
145 del_timer_sync(&heartbeat_data->timer); 151 del_timer_sync(&heartbeat_data->timer);
146 device_remove_file(led_cdev->dev, &dev_attr_invert); 152 device_remove_file(led_cdev->dev, &dev_attr_invert);
147 kfree(heartbeat_data); 153 kfree(heartbeat_data);
154 clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
148 led_cdev->activated = false; 155 led_cdev->activated = false;
149 } 156 }
150} 157}
diff --git a/include/linux/leds.h b/include/linux/leds.h
index 569cb531094c..38c0bd7ca107 100644
--- a/include/linux/leds.h
+++ b/include/linux/leds.h
@@ -13,6 +13,7 @@
13#define __LINUX_LEDS_H_INCLUDED 13#define __LINUX_LEDS_H_INCLUDED
14 14
15#include <linux/device.h> 15#include <linux/device.h>
16#include <linux/kernfs.h>
16#include <linux/list.h> 17#include <linux/list.h>
17#include <linux/mutex.h> 18#include <linux/mutex.h>
18#include <linux/rwsem.h> 19#include <linux/rwsem.h>
@@ -27,6 +28,7 @@ struct device;
27 28
28enum led_brightness { 29enum led_brightness {
29 LED_OFF = 0, 30 LED_OFF = 0,
31 LED_ON = 1,
30 LED_HALF = 127, 32 LED_HALF = 127,
31 LED_FULL = 255, 33 LED_FULL = 255,
32}; 34};
@@ -46,6 +48,7 @@ struct led_classdev {
46#define LED_DEV_CAP_FLASH (1 << 18) 48#define LED_DEV_CAP_FLASH (1 << 18)
47#define LED_HW_PLUGGABLE (1 << 19) 49#define LED_HW_PLUGGABLE (1 << 19)
48#define LED_PANIC_INDICATOR (1 << 20) 50#define LED_PANIC_INDICATOR (1 << 20)
51#define LED_BRIGHT_HW_CHANGED (1 << 21)
49 52
50 /* set_brightness_work / blink_timer flags, atomic, private. */ 53 /* set_brightness_work / blink_timer flags, atomic, private. */
51 unsigned long work_flags; 54 unsigned long work_flags;
@@ -110,6 +113,11 @@ struct led_classdev {
110 bool activated; 113 bool activated;
111#endif 114#endif
112 115
116#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
117 int brightness_hw_changed;
118 struct kernfs_node *brightness_hw_changed_kn;
119#endif
120
113 /* Ensures consistent access to the LED Flash Class device */ 121 /* Ensures consistent access to the LED Flash Class device */
114 struct mutex led_access; 122 struct mutex led_access;
115}; 123};
@@ -422,4 +430,12 @@ static inline void ledtrig_cpu(enum cpu_led_event evt)
422} 430}
423#endif 431#endif
424 432
433#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
434extern void led_classdev_notify_brightness_hw_changed(
435 struct led_classdev *led_cdev, enum led_brightness brightness);
436#else
437static inline void led_classdev_notify_brightness_hw_changed(
438 struct led_classdev *led_cdev, enum led_brightness brightness) { }
439#endif
440
425#endif /* __LINUX_LEDS_H_INCLUDED */ 441#endif /* __LINUX_LEDS_H_INCLUDED */
diff --git a/tools/leds/Makefile b/tools/leds/Makefile
index c03a79ebf9c8..078b666fd78b 100644
--- a/tools/leds/Makefile
+++ b/tools/leds/Makefile
@@ -3,11 +3,11 @@
3CC = $(CROSS_COMPILE)gcc 3CC = $(CROSS_COMPILE)gcc
4CFLAGS = -Wall -Wextra -g -I../../include/uapi 4CFLAGS = -Wall -Wextra -g -I../../include/uapi
5 5
6all: uledmon 6all: uledmon led_hw_brightness_mon
7%: %.c 7%: %.c
8 $(CC) $(CFLAGS) -o $@ $^ 8 $(CC) $(CFLAGS) -o $@ $^
9 9
10clean: 10clean:
11 $(RM) uledmon 11 $(RM) uledmon led_hw_brightness_mon
12 12
13.PHONY: all clean 13.PHONY: all clean
diff --git a/tools/leds/led_hw_brightness_mon.c b/tools/leds/led_hw_brightness_mon.c
new file mode 100644
index 000000000000..64642ccfe442
--- /dev/null
+++ b/tools/leds/led_hw_brightness_mon.c
@@ -0,0 +1,84 @@
1/*
2 * led_hw_brightness_mon.c
3 *
4 * This program monitors LED brightness level changes having its origin
5 * in hardware/firmware, i.e. outside of kernel control.
6 * A timestamp and brightness value is printed each time the brightness changes.
7 *
8 * Usage: led_hw_brightness_mon <device-name>
9 *
10 * <device-name> is the name of the LED class device to be monitored. Pressing
11 * CTRL+C will exit.
12 */
13
14#include <errno.h>
15#include <fcntl.h>
16#include <poll.h>
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
20#include <time.h>
21#include <unistd.h>
22
23#include <linux/uleds.h>
24
25int main(int argc, char const *argv[])
26{
27 int fd, ret;
28 char brightness_file_path[LED_MAX_NAME_SIZE + 11];
29 struct pollfd pollfd;
30 struct timespec ts;
31 char buf[11];
32
33 if (argc != 2) {
34 fprintf(stderr, "Requires <device-name> argument\n");
35 return 1;
36 }
37
38 snprintf(brightness_file_path, LED_MAX_NAME_SIZE,
39 "/sys/class/leds/%s/brightness_hw_changed", argv[1]);
40
41 fd = open(brightness_file_path, O_RDONLY);
42 if (fd == -1) {
43 printf("Failed to open %s file\n", brightness_file_path);
44 return 1;
45 }
46
47 /*
48 * read may fail if no hw brightness change has occurred so far,
49 * but it is required to avoid spurious poll notifications in
50 * the opposite case.
51 */
52 read(fd, buf, sizeof(buf));
53
54 pollfd.fd = fd;
55 pollfd.events = POLLPRI;
56
57 while (1) {
58 ret = poll(&pollfd, 1, -1);
59 if (ret == -1) {
60 printf("Failed to poll %s file (%d)\n",
61 brightness_file_path, ret);
62 ret = 1;
63 break;
64 }
65
66 clock_gettime(CLOCK_MONOTONIC, &ts);
67
68 ret = read(fd, buf, sizeof(buf));
69 if (ret < 0)
70 break;
71
72 ret = lseek(pollfd.fd, 0, SEEK_SET);
73 if (ret < 0) {
74 printf("lseek failed (%d)\n", ret);
75 break;
76 }
77
78 printf("[%ld.%09ld] %d\n", ts.tv_sec, ts.tv_nsec, atoi(buf));
79 }
80
81 close(fd);
82
83 return ret;
84}