UPSTREAM: leds: multicolor: Introduce a multicolor class definition

Introduce a multicolor class that groups colored LEDs
within a LED node.

The multicolor class groups monochrome LEDs and allows controlling two
aspects of the final combined color: hue and lightness. The former is
controlled via the intensity file and the latter is controlled
via brightness file.

Signed-off-by: Dan Murphy <[email protected]>
Acked-by: Jacek Anaszewski <[email protected]>
Signed-off-by: Pavel Machek <[email protected]>
[squashed leds: multicolor: Fix camel case in documentation in]

Bug: 260685629
(cherry picked from commit 55d5d3b46b08a4dc0b05343d24640744e7430ed7)
Change-Id: Ib1f41d74ace8e3a9c1071d52202c8d8b70a912e0
Signed-off-by: Farid Chahla <[email protected]>
This commit is contained in:
Dan Murphy 2020-07-16 13:19:56 -05:00 • committed by Treehugger Robot
commit 3e667a0854
7 changed files with 457 additions and 0 deletions

View file

@ -30,6 +30,16 @@ config LEDS_CLASS_FLASH
for the flash related features of a LED device. It can be built
as a module.
config LEDS_CLASS_MULTICOLOR
tristate "LED Multicolor Class Support"
depends on LEDS_CLASS
help
This option enables the multicolor LED sysfs class in /sys/class/leds.
It wraps LED class and adds multicolor LED specific sysfs attributes
and kernel internal API to it. You'll need this to provide support
for multicolor LEDs that are grouped together. This class is not
intended for single color LEDs. It can be built as a module.
config LEDS_BRIGHTNESS_HW_CHANGED
bool "LED Class brightness_hw_changed attribute support"
depends on LEDS_CLASS

View file

@ -4,6 +4,7 @@
obj-$(CONFIG_NEW_LEDS) += led-core.o
obj-$(CONFIG_LEDS_CLASS) += led-class.o
obj-$(CONFIG_LEDS_CLASS_FLASH) += led-class-flash.o
obj-$(CONFIG_LEDS_CLASS_MULTICOLOR) += led-class-multicolor.o
obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
# LED Platform Drivers

View file

@ -0,0 +1,203 @@
// SPDX-License-Identifier: GPL-2.0
// LED Multicolor class interface
// Copyright (C) 2019-20 Texas Instruments Incorporated - http://www.ti.com/
// Author: Dan Murphy <[email protected]>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/led-class-multicolor.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include "leds.h"
int led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev,
enum led_brightness brightness)
{
struct led_classdev *led_cdev = &mcled_cdev->led_cdev;
int i;
for (i = 0; i < mcled_cdev->num_colors; i++)
mcled_cdev->subled_info[i].brightness = brightness *
mcled_cdev->subled_info[i].intensity /
led_cdev->max_brightness;
return 0;
}
EXPORT_SYMBOL_GPL(led_mc_calc_color_components);
static ssize_t multi_intensity_store(struct device *dev,
struct device_attribute *intensity_attr,
const char *buf, size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev);
int nrchars, offset = 0;
int intensity_value[LED_COLOR_ID_MAX];
int i;
ssize_t ret;
mutex_lock(&led_cdev->led_access);
for (i = 0; i < mcled_cdev->num_colors; i++) {
ret = sscanf(buf + offset, "%i%n",
&intensity_value[i], &nrchars);
if (ret != 1) {
ret = -EINVAL;
goto err_out;
}
offset += nrchars;
}
offset++;
if (offset < size) {
ret = -EINVAL;
goto err_out;
}
for (i = 0; i < mcled_cdev->num_colors; i++)
mcled_cdev->subled_info[i].intensity = intensity_value[i];
led_set_brightness(led_cdev, led_cdev->brightness);
ret = size;
err_out:
mutex_unlock(&led_cdev->led_access);
return ret;
}
static ssize_t multi_intensity_show(struct device *dev,
struct device_attribute *intensity_attr,
char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev);
int len = 0;
int i;
for (i = 0; i < mcled_cdev->num_colors; i++) {
len += sprintf(buf + len, "%d",
mcled_cdev->subled_info[i].intensity);
if (i < mcled_cdev->num_colors - 1)
len += sprintf(buf + len, " ");
}
buf[len++] = '\n';
return len;
}
static DEVICE_ATTR_RW(multi_intensity);
static ssize_t multi_index_show(struct device *dev,
struct device_attribute *multi_index_attr,
char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev);
int len = 0;
int index;
int i;
for (i = 0; i < mcled_cdev->num_colors; i++) {
index = mcled_cdev->subled_info[i].color_index;
len += sprintf(buf + len, "%s", led_colors[index]);
if (i < mcled_cdev->num_colors - 1)
len += sprintf(buf + len, " ");
}
buf[len++] = '\n';
return len;
}
static DEVICE_ATTR_RO(multi_index);
static struct attribute *led_multicolor_attrs[] = {
&dev_attr_multi_intensity.attr,
&dev_attr_multi_index.attr,
NULL,
};
ATTRIBUTE_GROUPS(led_multicolor);
int led_classdev_multicolor_register_ext(struct device *parent,
struct led_classdev_mc *mcled_cdev,
struct led_init_data *init_data)
{
struct led_classdev *led_cdev;
if (!mcled_cdev)
return -EINVAL;
if (mcled_cdev->num_colors <= 0)
return -EINVAL;
if (mcled_cdev->num_colors > LED_COLOR_ID_MAX)
return -EINVAL;
led_cdev = &mcled_cdev->led_cdev;
mcled_cdev->led_cdev.groups = led_multicolor_groups;
return led_classdev_register_ext(parent, led_cdev, init_data);
}
EXPORT_SYMBOL_GPL(led_classdev_multicolor_register_ext);
void led_classdev_multicolor_unregister(struct led_classdev_mc *mcled_cdev)
{
if (!mcled_cdev)
return;
led_classdev_unregister(&mcled_cdev->led_cdev);
}
EXPORT_SYMBOL_GPL(led_classdev_multicolor_unregister);
static void devm_led_classdev_multicolor_release(struct device *dev, void *res)
{
led_classdev_multicolor_unregister(*(struct led_classdev_mc **)res);
}
int devm_led_classdev_multicolor_register_ext(struct device *parent,
struct led_classdev_mc *mcled_cdev,
struct led_init_data *init_data)
{
struct led_classdev_mc **dr;
int ret;
dr = devres_alloc(devm_led_classdev_multicolor_release,
sizeof(*dr), GFP_KERNEL);
if (!dr)
return -ENOMEM;
ret = led_classdev_multicolor_register_ext(parent, mcled_cdev,
init_data);
if (ret) {
devres_free(dr);
return ret;
}
*dr = mcled_cdev;
devres_add(parent, dr);
return 0;
}
EXPORT_SYMBOL_GPL(devm_led_classdev_multicolor_register_ext);
static int devm_led_classdev_multicolor_match(struct device *dev,
void *res, void *data)
{
struct led_classdev_mc **p = res;
if (WARN_ON(!p || !*p))
return 0;
return *p == data;
}
void devm_led_classdev_multicolor_unregister(struct device *dev,
struct led_classdev_mc *mcled_cdev)
{
WARN_ON(devres_release(dev,
devm_led_classdev_multicolor_release,
devm_led_classdev_multicolor_match, mcled_cdev));
}
EXPORT_SYMBOL_GPL(devm_led_classdev_multicolor_unregister);
MODULE_AUTHOR("Dan Murphy <[email protected]>");
MODULE_DESCRIPTION("Multicolor LED class interface");
MODULE_LICENSE("GPL v2");