forked from Minki/linux
LEDs for 4.11
-----BEGIN PGP SIGNATURE----- Version: GnuPG v2 iQIcBAABCAAGBQJYq0qJAAoJEL1qUBy3i3wmPCgP/3XMatJDziwedPfs1qZXNXPM LLFGsmCtSC79TXyWYg9jgDoLKbt2G8Rb1KAvfhhPd7H/HRQD0YjwEHEzYv6BYE7O yns6t1ea1pZUGtqwPjSwvVybdZZgOsDI8T6OxIv2Tj7m4sI9w26undFPnWF7dIyG Kv14fLncXh1q4iwaXSfjFokI3V+o1xUH/fvLaLQ7w0EgbQn6VTgdn+fJr6aM1LH5 tcMyBnmaRg4D++erXPeqYxhRtG+Fn3NGolFjDWAXAxyxooVcyD9zJpdx65I2T03U eVUI7gIpB/niOnskVTwJsd/hKI/aow3lDU+tRWgZvicuM6Sj7GI1zKuVU13aoKFw TwcAnEE7oQJFuU4AmbDlHjso31cWcOOStKNFQXjOP1qbU6O5Q3SWaYET5cWaUNWM hanY/hoU3U40x7m64HM67TT6frz6jEbNorY/bUvVX+JHrJcKTFFReBeIMgrIT/St 0wZNHptYeOjkLHB+jURgnBVm6bTc4DJjtjnCTFQ/kOOiPgo6CikXFzzBzJHgPI9L /NfOsT13v2R6BJlvHgjNqIbj1gRCFHSYGloI5PBWAG5hk5z38GcJPlqvWxCQWOXs NSfzLPUiavkfOW1U/OQTNHeeDZXKBuWMui1QlpTX3XkGPviDwtoGxezzgSAltdMo LxIt/v0G4q5xJV3nDR19 =amlj -----END PGP SIGNATURE----- Merge tag 'leds_for_4.11' of git://git.kernel.org/pub/scm/linux/kernel/git/j.anaszewski/linux-leds Pull LED updates from Jacek Anaszewski: "New features and improvements: - add new optional brightness_hw_changed attribute for the LEDs that may have their brightness level changed autonomously (outside of kernel control) by hardware / firmware. The attribute supports userspace notifications through POLLPRI events - add led_brightness_hw_mon tool that demonstrates how to use the aforementioned feature - add LED_ON enum for LEDs that can be only turned on/off, and don't allow setting other brightness levels - allow for adjusting heartbeat trigger blink brightness level Fixes and cleanups: - avoid harmless maybe-uninitialized warning in leds-ktd2692.c - add context to the existing example entries in common LED bindings to make the documentation more clear" * tag 'leds_for_4.11' of git://git.kernel.org/pub/scm/linux/kernel/git/j.anaszewski/linux-leds: leds: ledtrig-heartbeat: Make top brightness adjustable tools/leds: Add led_hw_brightness_mon program leds: class: Add new optional brightness_hw_changed attribute leds: ktd2692: avoid harmless maybe-uninitialized warning leds: add LED_ON brightness as boolean value DT: leds: Improve examples by adding some context
This commit is contained in:
commit
7aa7d60811
@ -23,6 +23,23 @@ Description:
|
||||
If the LED does not support different brightness levels, this
|
||||
should be 1.
|
||||
|
||||
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. Reading this file when no hw brightness change
|
||||
event has happened will return an ENODATA error.
|
||||
|
||||
What: /sys/class/leds/<led>/trigger
|
||||
Date: March 2006
|
||||
KernelVersion: 2.6.17
|
||||
|
@ -61,11 +61,18 @@ property can be omitted.
|
||||
|
||||
Examples:
|
||||
|
||||
gpio-leds {
|
||||
compatible = "gpio-leds";
|
||||
|
||||
system-status {
|
||||
label = "Status";
|
||||
linux,default-trigger = "heartbeat";
|
||||
...
|
||||
gpios = <&gpio0 0 GPIO_ACTIVE_HIGH>;
|
||||
};
|
||||
};
|
||||
|
||||
max77693-led {
|
||||
compatible = "maxim,max77693-led";
|
||||
|
||||
camera-flash {
|
||||
label = "Flash";
|
||||
@ -74,3 +81,4 @@ camera-flash {
|
||||
flash-max-microamp = <320000>;
|
||||
flash-max-timeout-us = <500000>;
|
||||
};
|
||||
};
|
||||
|
@ -65,6 +65,21 @@ LED subsystem core exposes following API for setting brightness:
|
||||
blinking, returns -EBUSY if software blink fallback is enabled.
|
||||
|
||||
|
||||
LED registration API
|
||||
====================
|
||||
|
||||
A driver wanting to register a LED classdev for use by other drivers /
|
||||
userspace needs to allocate and fill a led_classdev struct and then call
|
||||
[devm_]led_classdev_register. If the non devm version is used the driver
|
||||
must call led_classdev_unregister from its remove function before
|
||||
free-ing the led_classdev struct.
|
||||
|
||||
If the driver can detect hardware initiated brightness changes and thus
|
||||
wants to have a brightness_hw_changed attribute then the LED_BRIGHT_HW_CHANGED
|
||||
flag must be set in flags before registering. Calling
|
||||
led_classdev_notify_brightness_hw_changed on a classdev not registered with
|
||||
the LED_BRIGHT_HW_CHANGED flag is a bug and will trigger a WARN_ON.
|
||||
|
||||
Hardware accelerated blink of LEDs
|
||||
==================================
|
||||
|
||||
|
@ -29,6 +29,15 @@ config LEDS_CLASS_FLASH
|
||||
for the flash related features of a LED device. It can be built
|
||||
as a module.
|
||||
|
||||
config LEDS_BRIGHTNESS_HW_CHANGED
|
||||
bool "LED Class brightness_hw_changed attribute support"
|
||||
depends on LEDS_CLASS
|
||||
help
|
||||
This option enables support for the brightness_hw_changed attribute
|
||||
for led sysfs class devices under /sys/class/leds.
|
||||
|
||||
See Documentation/ABI/testing/sysfs-class-led for details.
|
||||
|
||||
comment "LED drivers"
|
||||
|
||||
config LEDS_88PM860X
|
||||
|
@ -103,6 +103,68 @@ static const struct attribute_group *led_groups[] = {
|
||||
NULL,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
|
||||
static ssize_t brightness_hw_changed_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
|
||||
if (led_cdev->brightness_hw_changed == -1)
|
||||
return -ENODATA;
|
||||
|
||||
return sprintf(buf, "%u\n", led_cdev->brightness_hw_changed);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(brightness_hw_changed);
|
||||
|
||||
static int led_add_brightness_hw_changed(struct led_classdev *led_cdev)
|
||||
{
|
||||
struct device *dev = led_cdev->dev;
|
||||
int ret;
|
||||
|
||||
ret = device_create_file(dev, &dev_attr_brightness_hw_changed);
|
||||
if (ret) {
|
||||
dev_err(dev, "Error creating brightness_hw_changed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
led_cdev->brightness_hw_changed_kn =
|
||||
sysfs_get_dirent(dev->kobj.sd, "brightness_hw_changed");
|
||||
if (!led_cdev->brightness_hw_changed_kn) {
|
||||
dev_err(dev, "Error getting brightness_hw_changed kn\n");
|
||||
device_remove_file(dev, &dev_attr_brightness_hw_changed);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev)
|
||||
{
|
||||
sysfs_put(led_cdev->brightness_hw_changed_kn);
|
||||
device_remove_file(led_cdev->dev, &dev_attr_brightness_hw_changed);
|
||||
}
|
||||
|
||||
void led_classdev_notify_brightness_hw_changed(struct led_classdev *led_cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
if (WARN_ON(!led_cdev->brightness_hw_changed_kn))
|
||||
return;
|
||||
|
||||
led_cdev->brightness_hw_changed = brightness;
|
||||
sysfs_notify_dirent(led_cdev->brightness_hw_changed_kn);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(led_classdev_notify_brightness_hw_changed);
|
||||
#else
|
||||
static int led_add_brightness_hw_changed(struct led_classdev *led_cdev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* led_classdev_suspend - suspend an led_classdev.
|
||||
* @led_cdev: the led_classdev to suspend.
|
||||
@ -204,9 +266,20 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
|
||||
dev_warn(parent, "Led %s renamed to %s due to name collision",
|
||||
led_cdev->name, dev_name(led_cdev->dev));
|
||||
|
||||
if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) {
|
||||
ret = led_add_brightness_hw_changed(led_cdev);
|
||||
if (ret) {
|
||||
device_unregister(led_cdev->dev);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
led_cdev->work_flags = 0;
|
||||
#ifdef CONFIG_LEDS_TRIGGERS
|
||||
init_rwsem(&led_cdev->trigger_lock);
|
||||
#endif
|
||||
#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
|
||||
led_cdev->brightness_hw_changed = -1;
|
||||
#endif
|
||||
mutex_init(&led_cdev->led_access);
|
||||
/* add to the list of leds */
|
||||
@ -256,6 +329,9 @@ void led_classdev_unregister(struct led_classdev *led_cdev)
|
||||
|
||||
flush_work(&led_cdev->set_brightness_work);
|
||||
|
||||
if (led_cdev->flags & LED_BRIGHT_HW_CHANGED)
|
||||
led_remove_brightness_hw_changed(led_cdev);
|
||||
|
||||
device_unregister(led_cdev->dev);
|
||||
|
||||
down_write(&leds_list_lock);
|
||||
|
@ -270,15 +270,15 @@ static int ktd2692_parse_dt(struct ktd2692_context *led, struct device *dev,
|
||||
return -ENXIO;
|
||||
|
||||
led->ctrl_gpio = devm_gpiod_get(dev, "ctrl", GPIOD_ASIS);
|
||||
if (IS_ERR(led->ctrl_gpio)) {
|
||||
ret = PTR_ERR(led->ctrl_gpio);
|
||||
ret = PTR_ERR_OR_ZERO(led->ctrl_gpio);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot get ctrl-gpios %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
led->aux_gpio = devm_gpiod_get(dev, "aux", GPIOD_ASIS);
|
||||
if (IS_ERR(led->aux_gpio)) {
|
||||
ret = PTR_ERR(led->aux_gpio);
|
||||
ret = PTR_ERR_OR_ZERO(led->aux_gpio);
|
||||
if (ret) {
|
||||
dev_err(dev, "cannot get aux-gpios %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
@ -43,6 +43,9 @@ static void led_heartbeat_function(unsigned long data)
|
||||
return;
|
||||
}
|
||||
|
||||
if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE, &led_cdev->work_flags))
|
||||
led_cdev->blink_brightness = led_cdev->new_blink_brightness;
|
||||
|
||||
/* acts like an actual heart beat -- ie thump-thump-pause... */
|
||||
switch (heartbeat_data->phase) {
|
||||
case 0:
|
||||
@ -59,26 +62,26 @@ static void led_heartbeat_function(unsigned long data)
|
||||
delay = msecs_to_jiffies(70);
|
||||
heartbeat_data->phase++;
|
||||
if (!heartbeat_data->invert)
|
||||
brightness = led_cdev->max_brightness;
|
||||
brightness = led_cdev->blink_brightness;
|
||||
break;
|
||||
case 1:
|
||||
delay = heartbeat_data->period / 4 - msecs_to_jiffies(70);
|
||||
heartbeat_data->phase++;
|
||||
if (heartbeat_data->invert)
|
||||
brightness = led_cdev->max_brightness;
|
||||
brightness = led_cdev->blink_brightness;
|
||||
break;
|
||||
case 2:
|
||||
delay = msecs_to_jiffies(70);
|
||||
heartbeat_data->phase++;
|
||||
if (!heartbeat_data->invert)
|
||||
brightness = led_cdev->max_brightness;
|
||||
brightness = led_cdev->blink_brightness;
|
||||
break;
|
||||
default:
|
||||
delay = heartbeat_data->period - heartbeat_data->period / 4 -
|
||||
msecs_to_jiffies(70);
|
||||
heartbeat_data->phase = 0;
|
||||
if (heartbeat_data->invert)
|
||||
brightness = led_cdev->max_brightness;
|
||||
brightness = led_cdev->blink_brightness;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -133,7 +136,10 @@ static void heartbeat_trig_activate(struct led_classdev *led_cdev)
|
||||
setup_timer(&heartbeat_data->timer,
|
||||
led_heartbeat_function, (unsigned long) led_cdev);
|
||||
heartbeat_data->phase = 0;
|
||||
if (!led_cdev->blink_brightness)
|
||||
led_cdev->blink_brightness = led_cdev->max_brightness;
|
||||
led_heartbeat_function(heartbeat_data->timer.data);
|
||||
set_bit(LED_BLINK_SW, &led_cdev->work_flags);
|
||||
led_cdev->activated = true;
|
||||
}
|
||||
|
||||
@ -145,6 +151,7 @@ static void heartbeat_trig_deactivate(struct led_classdev *led_cdev)
|
||||
del_timer_sync(&heartbeat_data->timer);
|
||||
device_remove_file(led_cdev->dev, &dev_attr_invert);
|
||||
kfree(heartbeat_data);
|
||||
clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
|
||||
led_cdev->activated = false;
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@
|
||||
#define __LINUX_LEDS_H_INCLUDED
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/kernfs.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/rwsem.h>
|
||||
@ -27,6 +28,7 @@ struct device;
|
||||
|
||||
enum led_brightness {
|
||||
LED_OFF = 0,
|
||||
LED_ON = 1,
|
||||
LED_HALF = 127,
|
||||
LED_FULL = 255,
|
||||
};
|
||||
@ -46,6 +48,7 @@ struct led_classdev {
|
||||
#define LED_DEV_CAP_FLASH (1 << 18)
|
||||
#define LED_HW_PLUGGABLE (1 << 19)
|
||||
#define LED_PANIC_INDICATOR (1 << 20)
|
||||
#define LED_BRIGHT_HW_CHANGED (1 << 21)
|
||||
|
||||
/* set_brightness_work / blink_timer flags, atomic, private. */
|
||||
unsigned long work_flags;
|
||||
@ -110,6 +113,11 @@ struct led_classdev {
|
||||
bool activated;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
|
||||
int brightness_hw_changed;
|
||||
struct kernfs_node *brightness_hw_changed_kn;
|
||||
#endif
|
||||
|
||||
/* Ensures consistent access to the LED Flash Class device */
|
||||
struct mutex led_access;
|
||||
};
|
||||
@ -422,4 +430,12 @@ static inline void ledtrig_cpu(enum cpu_led_event evt)
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
|
||||
extern void led_classdev_notify_brightness_hw_changed(
|
||||
struct led_classdev *led_cdev, enum led_brightness brightness);
|
||||
#else
|
||||
static inline void led_classdev_notify_brightness_hw_changed(
|
||||
struct led_classdev *led_cdev, enum led_brightness brightness) { }
|
||||
#endif
|
||||
|
||||
#endif /* __LINUX_LEDS_H_INCLUDED */
|
||||
|
@ -3,11 +3,11 @@
|
||||
CC = $(CROSS_COMPILE)gcc
|
||||
CFLAGS = -Wall -Wextra -g -I../../include/uapi
|
||||
|
||||
all: uledmon
|
||||
all: uledmon led_hw_brightness_mon
|
||||
%: %.c
|
||||
$(CC) $(CFLAGS) -o $@ $^
|
||||
|
||||
clean:
|
||||
$(RM) uledmon
|
||||
$(RM) uledmon led_hw_brightness_mon
|
||||
|
||||
.PHONY: all clean
|
||||
|
84
tools/leds/led_hw_brightness_mon.c
Normal file
84
tools/leds/led_hw_brightness_mon.c
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* led_hw_brightness_mon.c
|
||||
*
|
||||
* This program monitors LED brightness level changes having its origin
|
||||
* in hardware/firmware, i.e. outside of kernel control.
|
||||
* A timestamp and brightness value is printed each time the brightness changes.
|
||||
*
|
||||
* Usage: led_hw_brightness_mon <device-name>
|
||||
*
|
||||
* <device-name> is the name of the LED class device to be monitored. Pressing
|
||||
* CTRL+C will exit.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <linux/uleds.h>
|
||||
|
||||
int main(int argc, char const *argv[])
|
||||
{
|
||||
int fd, ret;
|
||||
char brightness_file_path[LED_MAX_NAME_SIZE + 11];
|
||||
struct pollfd pollfd;
|
||||
struct timespec ts;
|
||||
char buf[11];
|
||||
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "Requires <device-name> argument\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
snprintf(brightness_file_path, LED_MAX_NAME_SIZE,
|
||||
"/sys/class/leds/%s/brightness_hw_changed", argv[1]);
|
||||
|
||||
fd = open(brightness_file_path, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
printf("Failed to open %s file\n", brightness_file_path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* read may fail if no hw brightness change has occurred so far,
|
||||
* but it is required to avoid spurious poll notifications in
|
||||
* the opposite case.
|
||||
*/
|
||||
read(fd, buf, sizeof(buf));
|
||||
|
||||
pollfd.fd = fd;
|
||||
pollfd.events = POLLPRI;
|
||||
|
||||
while (1) {
|
||||
ret = poll(&pollfd, 1, -1);
|
||||
if (ret == -1) {
|
||||
printf("Failed to poll %s file (%d)\n",
|
||||
brightness_file_path, ret);
|
||||
ret = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
|
||||
ret = read(fd, buf, sizeof(buf));
|
||||
if (ret < 0)
|
||||
break;
|
||||
|
||||
ret = lseek(pollfd.fd, 0, SEEK_SET);
|
||||
if (ret < 0) {
|
||||
printf("lseek failed (%d)\n", ret);
|
||||
break;
|
||||
}
|
||||
|
||||
printf("[%ld.%09ld] %d\n", ts.tv_sec, ts.tv_nsec, atoi(buf));
|
||||
}
|
||||
|
||||
close(fd);
|
||||
|
||||
return ret;
|
||||
}
|
Loading…
Reference in New Issue
Block a user