diff options
| -rw-r--r-- | Documentation/ABI/testing/sysfs-class-led | 17 | ||||
| -rw-r--r-- | Documentation/devicetree/bindings/leds/common.txt | 28 | ||||
| -rw-r--r-- | Documentation/leds/leds-class.txt | 15 | ||||
| -rw-r--r-- | drivers/leds/Kconfig | 9 | ||||
| -rw-r--r-- | drivers/leds/led-class.c | 76 | ||||
| -rw-r--r-- | drivers/leds/leds-ktd2692.c | 8 | ||||
| -rw-r--r-- | drivers/leds/trigger/ledtrig-heartbeat.c | 15 | ||||
| -rw-r--r-- | include/linux/leds.h | 16 | ||||
| -rw-r--r-- | tools/leds/Makefile | 4 | ||||
| -rw-r--r-- | tools/leds/led_hw_brightness_mon.c | 84 |
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 | ||
| 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 | What: /sys/class/leds/<led>/trigger | 43 | What: /sys/class/leds/<led>/trigger |
| 27 | Date: March 2006 | 44 | Date: March 2006 |
| 28 | KernelVersion: 2.6.17 | 45 | KernelVersion: 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 | ||
| 62 | Examples: | 62 | Examples: |
| 63 | 63 | ||
| 64 | system-status { | 64 | gpio-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 | ||
| 70 | camera-flash { | 74 | max77693-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 | ||
| 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 | Hardware accelerated blink of LEDs | 83 | Hardware 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 | ||
| 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 | comment "LED drivers" | 41 | comment "LED drivers" |
| 33 | 42 | ||
| 34 | config LEDS_88PM860X | 43 | config 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 | ||
| 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 | * 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 | ||
| 28 | enum led_brightness { | 29 | enum 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 | ||
| 434 | extern void led_classdev_notify_brightness_hw_changed( | ||
| 435 | struct led_classdev *led_cdev, enum led_brightness brightness); | ||
| 436 | #else | ||
| 437 | static 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 @@ | |||
| 3 | CC = $(CROSS_COMPILE)gcc | 3 | CC = $(CROSS_COMPILE)gcc |
| 4 | CFLAGS = -Wall -Wextra -g -I../../include/uapi | 4 | CFLAGS = -Wall -Wextra -g -I../../include/uapi |
| 5 | 5 | ||
| 6 | all: uledmon | 6 | all: uledmon led_hw_brightness_mon |
| 7 | %: %.c | 7 | %: %.c |
| 8 | $(CC) $(CFLAGS) -o $@ $^ | 8 | $(CC) $(CFLAGS) -o $@ $^ |
| 9 | 9 | ||
| 10 | clean: | 10 | clean: |
| 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 | |||
| 25 | int 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 | } | ||
