Skip to content

Commit

Permalink
drivers: leds: Add core support for multi color element LEDs
Browse files Browse the repository at this point in the history
Adds support to manage multi color element LEDs into led core and led class.

For user space new sysfs nodes 'color' and 'max_color' are provided allowing
atomic change for color elements and brightnes of whole LED.

For kernel space adds new color element data structures and helpers are also
provided to aid in keeping logic low in LED drivers and let LED core to provide
support for calculating brightness for individual LED elements.

Signed-off-by: Vesa Jääskeläinen <vesa.jaaskelainen@vaisala.com>
  • Loading branch information
vesajaaskelainen committed Jan 1, 2019
1 parent 8fe28cb commit 55d5539
Show file tree
Hide file tree
Showing 5 changed files with 352 additions and 1 deletion.
52 changes: 52 additions & 0 deletions Documentation/leds/leds-class.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ LED is defined in max_brightness file. The brightness file will set the brightne
of the LED (taking a value 0-max_brightness). Most LEDs don't have hardware
brightness support so will just be turned on for non-zero brightness settings.

The class supports multi color element LEDs allowing individual LED element
color control in collaboration with brigthtness control. User can configure LED
to specific color by configured individual color elements. Brightness is
virtual scalar and is used to calculate raw color element value. Raw color
element values are used to configure physical color elements in LEDs.

The class also introduces the optional concept of an LED trigger. A trigger
is a kernel based source of led events. Triggers can either be simple or
complex. A simple trigger isn't configurable and is designed to slot into
Expand Down Expand Up @@ -65,6 +71,18 @@ LED subsystem core exposes following API for setting brightness:
blinking, returns -EBUSY if software blink fallback is enabled.


Multi color element API
=======================

LED subsystem core exposes following API for managing multi color element LEDs:

- led_color_element_setup_of : is a helper for configuring color element
in led core based on device tree settings.
- led_scale_color_elements : for scaling color elements based on brightness
value. Used by drivers when they need to have fresh raw color element
values.


LED registration API
====================

Expand All @@ -80,6 +98,10 @@ 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.

If the driver supports multi color element LEDs it needs to configure
led device with LED_MULTI_COLOR_LED in flags.


Hardware accelerated blink of LEDs
==================================

Expand All @@ -103,6 +125,36 @@ should completely turn off the LED and cancel the previously programmed
hardware blinking function, if any.


Multi color element support
===========================

To enable multi color element support it needs to be enabled with
LEDS_MULTI_COLOR in kernel config.

Multi color leds need to be pre-configured in ledclass structures before
registering new LED to LED core. LED core provides helper
led_color_element_setup_of to help in defining settings based on device tree
configuration.

Driver needs to set/calculate proper values for individual color elements based
on use configuration. User has ability to configure individual color elements
and overall brightness of the LED. LED core supports virtual brightness support
and provides helper led_scale_color_elements for LED drivers for linearly scaling
indivudual color elements.

From user space multi color element LEDs can be configured using sysfs entries:

- color : current multi color element values with encoding:
brightness=<value> [<color element>=<value> ...]
Individual settings can be changed or all settings at once.
Reading the sysfs entry provides current settings in use.
- max_color : maximul values for multi color elements with encoding:
brightness=<max value> [<color element>=<max value> ...]

Multi color element LEDs support also triggers as control brightness value and
that will cause new color element values to be controlled.


Known Issues
============

Expand Down
9 changes: 9 additions & 0 deletions drivers/leds/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ config LEDS_BRIGHTNESS_HW_CHANGED

See Documentation/ABI/testing/sysfs-class-led for details.

config LEDS_MULTI_COLOR
bool "LED Multi-color Support"
depends on LEDS_CLASS
help
This option enables multi-color led sysfs class in /sys/class/leds.
It wrapps LED Class and adds multi-color LEDs specific sysfs attributes
and kernel internal API to it. You'll need this to provide support
for multi-color related features of a LED device.

comment "LED drivers"

config LEDS_88PM860X
Expand Down
182 changes: 182 additions & 0 deletions drivers/leds/led-class.c
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,172 @@ static ssize_t max_brightness_show(struct device *dev,
}
static DEVICE_ATTR_RO(max_brightness);

#ifdef CONFIG_LEDS_MULTI_COLOR
static int color_element_name2index(struct led_classdev *led_cdev,
const char *name)
{
unsigned int i;

for (i = 0; i < led_cdev->num_color_elements; i++) {
if (strcmp(led_cdev->color_elements[i].name, name) == 0)
return i;
}

return -ENOENT;
}

static int set_color_element_value(struct led_classdev *led_cdev,
int element_index,
unsigned long value)
{
if (element_index < 0 || element_index >= led_cdev->num_color_elements)
return -ENOENT;

if (value > led_cdev->color_elements[element_index].max_value)
return -EINVAL;

led_cdev->color_elements[element_index].value = (unsigned int)value;
return 0;
}

static ssize_t color_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
unsigned int i;
ssize_t len;

len = sprintf(buf, "brightness=%u", led_cdev->brightness);
for (i = 0; i < led_cdev->num_color_elements; i++) {
len += sprintf(&buf[len], " %s=%u",
led_cdev->color_elements[i].name,
led_cdev->color_elements[i].value);
}
len += sprintf(&buf[len], "\n");

return len;
}

static ssize_t color_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
unsigned long val;
unsigned long brightness;
ssize_t ret;
char *bufcopy = 0;
char *elem;
char *ptr;

mutex_lock(&led_cdev->led_access);

if (led_sysfs_is_disabled(led_cdev)) {
ret = -EBUSY;
goto unlock;
}

brightness = led_cdev->brightness;

bufcopy = kstrdup(buf, GFP_KERNEL);
ptr = bufcopy;

while ((elem = strsep(&ptr, " "))) {
char *value = elem;
char *key;

key = strsep(&value, "=");

if (value) {
int element_index;

element_index = color_element_name2index(led_cdev, key);
if (element_index >= 0) {
ret = kstrtoul(value, 10, &val);
if (ret)
goto unlock;

ret = set_color_element_value(led_cdev,
element_index,
val);
if (ret)
goto unlock;
} else if (strcmp(key, "brightness") == 0) {
ret = kstrtoul(value, 10, &brightness);
if (ret)
goto unlock;
} else {
ret = -EINVAL;
goto unlock;
}
}
}

if (brightness == LED_OFF)
led_trigger_remove(led_cdev);
led_set_brightness(led_cdev, brightness);

ret = size;
unlock:
kfree(bufcopy);
mutex_unlock(&led_cdev->led_access);
return ret;
}
static DEVICE_ATTR_RW(color);

static ssize_t max_color_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
unsigned int i;
ssize_t len;

len = sprintf(buf, "brightness=%u", led_cdev->max_brightness);
for (i = 0; i < led_cdev->num_color_elements; i++) {
len += sprintf(&buf[len], " %s=%u",
led_cdev->color_elements[i].name,
led_cdev->color_elements[i].max_value);
}
len += sprintf(&buf[len], "\n");

return len;
}
static DEVICE_ATTR_RO(max_color);

static struct attribute *led_multi_color_attrs[] = {
&dev_attr_color.attr,
&dev_attr_max_color.attr,
NULL,
};

static const struct attribute_group led_multi_color_group = {
.attrs = led_multi_color_attrs,
};

static const struct attribute_group *led_multi_color_groups[] = {
&led_multi_color_group,
NULL,
};

static int led_add_multi_color_led(struct led_classdev *led_cdev)
{
struct device *dev = led_cdev->dev;
int ret;

ret = device_add_groups(dev, led_multi_color_groups);
if (ret) {
dev_err(dev, "Error creating multi-color-led sysfs nodes\n");
return ret;
}

return 0;
}

static void led_remove_multi_color_led(struct led_classdev *led_cdev)
{
device_remove_groups(led_cdev->dev, led_multi_color_groups);
}
#endif

#ifdef CONFIG_LEDS_TRIGGERS
static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store);
static struct attribute *led_trigger_attrs[] = {
Expand Down Expand Up @@ -283,6 +449,17 @@ int of_led_classdev_register(struct device *parent, struct device_node *np,
}
}

#ifdef CONFIG_LEDS_MULTI_COLOR
if (led_cdev->flags & LED_MULTI_COLOR_LED) {
ret = led_add_multi_color_led(led_cdev);
if (ret) {
device_unregister(led_cdev->dev);
mutex_unlock(&led_cdev->led_access);
return ret;
}
}
#endif

led_cdev->work_flags = 0;
#ifdef CONFIG_LEDS_TRIGGERS
init_rwsem(&led_cdev->trigger_lock);
Expand Down Expand Up @@ -339,6 +516,11 @@ void led_classdev_unregister(struct led_classdev *led_cdev)

flush_work(&led_cdev->set_brightness_work);

#ifdef CONFIG_LEDS_MULTI_COLOR
if (led_cdev->flags & LED_MULTI_COLOR_LED)
led_remove_multi_color_led(led_cdev);
#endif

if (led_cdev->flags & LED_BRIGHT_HW_CHANGED)
led_remove_brightness_hw_changed(led_cdev);

Expand Down
63 changes: 63 additions & 0 deletions drivers/leds/led-core.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/rwsem.h>
#include <linux/of.h>
#include "leds.h"

DECLARE_RWSEM(leds_list_lock);
Expand Down Expand Up @@ -310,6 +311,68 @@ int led_update_brightness(struct led_classdev *led_cdev)
}
EXPORT_SYMBOL_GPL(led_update_brightness);

#ifdef CONFIG_LEDS_MULTI_COLOR
int led_color_element_setup_of(struct device *dev,
struct led_classdev *led_cdev,
unsigned int elem_index,
struct device_node *elem_child)
{
struct led_color_element *color_element;
const char *elem_name;
u32 max_value;
u32 default_value;
int ret;

if (elem_index > led_cdev->num_color_elements)
return -EFAULT;

if (strncmp(elem_child->name, "element-", 8))
return -EINVAL;

elem_name = &elem_child->name[8];

if (strlen(elem_name) == 0)
return -EINVAL;

color_element = &led_cdev->color_elements[elem_index];

color_element->name = devm_kstrdup(dev, elem_name, GFP_KERNEL);

ret = of_property_read_u32(elem_child, "max-value", &max_value);
if (ret == 0)
color_element->max_value = max_value;

ret = of_property_read_u32(elem_child, "default-value", &default_value);
if (ret == 0)
color_element->value = default_value;

if (!color_element->max_value)
color_element->max_value = LED_FULL;

return 0;
}
EXPORT_SYMBOL_GPL(led_color_element_setup_of);

void led_scale_color_elements(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
unsigned long long max = led_cdev->max_brightness;
unsigned int i;

for (i = 0; i < led_cdev->num_color_elements; i++) {
unsigned long long value;

value = led_cdev->color_elements[i].value;

value *= brightness;
do_div(value, max);

led_cdev->color_elements[i].raw_value = (unsigned int)value;
}
}
EXPORT_SYMBOL_GPL(led_scale_color_elements);
#endif

/* Caller must ensure led_cdev->led_access held */
void led_sysfs_disable(struct led_classdev *led_cdev)
{
Expand Down
Loading

0 comments on commit 55d5539

Please sign in to comment.