Introduce a simple RGB framework that will allow devices to cluster and control RGB LEDs as a single LED. This simple framework exposes 4 files for control. color - This will set all RGB values to obtain a certain color red - This will set the red LED brightness green - This will set the green LED brightness blue - This will set the blue LED brightness Signed-off-by: Dan Murphy <dmurphy@xxxxxx> --- drivers/leds/Kconfig | 9 + drivers/leds/Makefile | 1 + drivers/leds/led-class-rgb.c | 313 ++++++++++++++++++++++++++++++++++ include/linux/led-class-rgb.h | 89 ++++++++++ 4 files changed, 412 insertions(+) create mode 100644 drivers/leds/led-class-rgb.c create mode 100644 include/linux/led-class-rgb.h diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index a72f97fca57b..f2d8bb7f4a6d 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -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_CLASS_RGB + tristate "LED RGB Class Support" + depends on LEDS_CLASS + help + This option enables the RGB led sysfs class in /sys/class/leds. + It wrapps LED Class and adds RGB LEDs specific sysfs attributes + and kernel internal API to it. You'll need this to provide support + for the clustered RGB LED devices. It can be built as a module. + config LEDS_BRIGHTNESS_HW_CHANGED bool "LED Class brightness_hw_changed attribute support" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 4c1b0054f379..5806760f00cb 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -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_RGB) += led-class-rgb.o obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o # LED Platform Drivers diff --git a/drivers/leds/led-class-rgb.c b/drivers/leds/led-class-rgb.c new file mode 100644 index 000000000000..a50beafbbd21 --- /dev/null +++ b/drivers/leds/led-class-rgb.c @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: GPL-2.0 +/* LED RGB class interface + * Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/ + */ + +#include <linux/device.h> +#include <linux/init.h> +#include <linux/led-class-rgb.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/slab.h> +#include "leds.h" + +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); + struct led_classdev_rgb *rgbled_cdev = lcdev_to_rgbcdev(led_cdev); + struct led_rgb_colors *colors = &rgbled_cdev->rgb_colors; + const struct led_rgb_ops *ops = rgbled_cdev->ops; + int red, green, blue; + ssize_t ret = -EINVAL; + + mutex_lock(&led_cdev->led_access); + + if (led_sysfs_is_disabled(led_cdev)) { + ret = -EBUSY; + goto unlock; + } + + if (sscanf(buf, "%d %d %d", &red, &green, &blue) != 3) { + pr_err("%s:unable to parse input\n", __func__); + return -1; + } + + /* Should these values be retainable if the ops fails should the old + * values be restored? + */ + colors->red = red; + colors->green = green; + colors->blue = blue; + + ret = ops->set_color(rgbled_cdev); + if (ret < 0) + goto unlock; + + ret = size; +unlock: + mutex_unlock(&led_cdev->led_access); + return ret; +} + +static ssize_t color_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_rgb *rgbled_cdev = lcdev_to_rgbcdev(led_cdev); + u8 red, green, blue; + + red = rgbled_cdev->rgb_colors.red; + green = rgbled_cdev->rgb_colors.green; + blue = rgbled_cdev->rgb_colors.blue; + + return sprintf(buf, "%d %d %d\n", red, green, blue); +} +static DEVICE_ATTR_RW(color); + +static struct attribute *led_set_color_attrs[] = { + &dev_attr_color.attr, + NULL, +}; + +static const struct attribute_group led_color_group = { + .attrs = led_set_color_attrs, +}; + +static ssize_t red_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_rgb *rgbled_cdev = lcdev_to_rgbcdev(led_cdev); + struct led_rgb_colors *colors = &rgbled_cdev->rgb_colors; + const struct led_rgb_ops *ops = rgbled_cdev->ops; + unsigned long state; + ssize_t ret = -EINVAL; + + mutex_lock(&led_cdev->led_access); + + if (led_sysfs_is_disabled(led_cdev)) { + ret = -EBUSY; + goto unlock; + } + + ret = kstrtoul(buf, 10, &state); + if (ret) + goto unlock; + + if (state > LED_FULL) { + ret = -EINVAL; + goto unlock; + } + + ret = ops->set_red_brightness(rgbled_cdev, state); + if (ret < 0) + goto unlock; + + colors->red = state; + + ret = size; +unlock: + mutex_unlock(&led_cdev->led_access); + return ret; +} + +static ssize_t red_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_rgb *rgbled_cdev = lcdev_to_rgbcdev(led_cdev); + struct led_rgb_colors *colors = &rgbled_cdev->rgb_colors; + + if (rgbled_cdev->ops->get_red_brightness) + colors->red = rgbled_cdev->ops->get_red_brightness(rgbled_cdev); + + return sprintf(buf, "%d\n", colors->red); +} +static DEVICE_ATTR_RW(red); + +static struct attribute *led_color_red_attrs[] = { + &dev_attr_red.attr, + NULL, +}; + +static const struct attribute_group led_set_red_group = { + .attrs = led_color_red_attrs, +}; +static ssize_t green_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_rgb *rgbled_cdev = lcdev_to_rgbcdev(led_cdev); + struct led_rgb_colors *colors = &rgbled_cdev->rgb_colors; + const struct led_rgb_ops *ops = rgbled_cdev->ops; + unsigned long state; + ssize_t ret = -EINVAL; + + mutex_lock(&led_cdev->led_access); + + if (led_sysfs_is_disabled(led_cdev)) { + ret = -EBUSY; + goto unlock; + } + + ret = kstrtoul(buf, 10, &state); + if (ret) + goto unlock; + + if (state > LED_FULL) { + ret = -EINVAL; + goto unlock; + } + + ret = ops->set_green_brightness(rgbled_cdev, state); + if (ret < 0) + goto unlock; + + colors->green = state; + ret = size; +unlock: + mutex_unlock(&led_cdev->led_access); + return ret; +} + +static ssize_t green_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_rgb *rgbled_cdev = lcdev_to_rgbcdev(led_cdev); + struct led_rgb_colors *colors = &rgbled_cdev->rgb_colors; + + if (rgbled_cdev->ops->get_green_brightness) + colors->green = rgbled_cdev->ops->get_green_brightness(rgbled_cdev); + + return sprintf(buf, "%d\n", colors->green); +} +static DEVICE_ATTR_RW(green); + +static struct attribute *led_color_green_attrs[] = { + &dev_attr_green.attr, + NULL, +}; + +static const struct attribute_group led_set_green_group = { + .attrs = led_color_green_attrs, +}; + +static ssize_t blue_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_rgb *rgbled_cdev = lcdev_to_rgbcdev(led_cdev); + const struct led_rgb_ops *ops = rgbled_cdev->ops; + struct led_rgb_colors *colors = &rgbled_cdev->rgb_colors; + unsigned long state; + ssize_t ret = -EINVAL; + + mutex_lock(&led_cdev->led_access); + + if (led_sysfs_is_disabled(led_cdev)) { + ret = -EBUSY; + goto unlock; + } + + ret = kstrtoul(buf, 10, &state); + if (ret) + goto unlock; + + if (state > LED_FULL) { + ret = -EINVAL; + goto unlock; + } + + ret = ops->set_blue_brightness(rgbled_cdev, state); + if (ret < 0) + goto unlock; + + colors->blue = state; + ret = size; +unlock: + mutex_unlock(&led_cdev->led_access); + return ret; +} + +static ssize_t blue_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_rgb *rgbled_cdev = lcdev_to_rgbcdev(led_cdev); + struct led_rgb_colors *colors = &rgbled_cdev->rgb_colors; + + if (rgbled_cdev->ops->get_blue_brightness) + colors->blue = rgbled_cdev->ops->get_blue_brightness(rgbled_cdev); + + return sprintf(buf, "%d\n", colors->blue); +} +static DEVICE_ATTR_RW(blue); + +static struct attribute *led_color_blue_attrs[] = { + &dev_attr_blue.attr, + NULL, +}; + +static const struct attribute_group led_set_blue_group = { + .attrs = led_color_blue_attrs, +}; + +static void led_rgb_init_sysfs_groups(struct led_classdev_rgb *rgbled_cdev) +{ + struct led_classdev *led_cdev = &rgbled_cdev->led_cdev; + const struct led_rgb_ops *ops = rgbled_cdev->ops; + const struct attribute_group **rgb_groups = rgbled_cdev->sysfs_groups; + + int num_sysfs_groups = 0; + + rgb_groups[num_sysfs_groups++] = &led_color_group; + + if (ops->set_red_brightness) + rgb_groups[num_sysfs_groups++] = &led_set_red_group; + + if (ops->set_green_brightness) + rgb_groups[num_sysfs_groups++] = &led_set_green_group; + + if (ops->set_blue_brightness) + rgb_groups[num_sysfs_groups++] = &led_set_blue_group; + + led_cdev->groups = rgb_groups; +} + +int led_classdev_rgb_register(struct device *parent, + struct led_classdev_rgb *rgbled_cdev) +{ + struct led_classdev *led_cdev; + struct led_rgb_ops *ops; + + if (!rgbled_cdev) + return -EINVAL; + + ops = rgbled_cdev->ops; + if (!ops || !ops->set_color) + return -EINVAL; + + led_cdev = &rgbled_cdev->led_cdev; + + /* Select the sysfs attributes to be created for the device */ + led_rgb_init_sysfs_groups(rgbled_cdev); + + /* Register led class device */ + return led_classdev_register(parent, led_cdev); +} +EXPORT_SYMBOL_GPL(led_classdev_rgb_register); + +void led_classdev_rgb_unregister(struct led_classdev_rgb *rgbled_cdev) +{ + if (!rgbled_cdev) + return; + + led_classdev_unregister(&rgbled_cdev->led_cdev); +} +EXPORT_SYMBOL_GPL(led_classdev_rgb_unregister); + +MODULE_AUTHOR("Dan Murphy <dmurphy@xxxxxx>"); +MODULE_DESCRIPTION("RGB LED class interface"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/led-class-rgb.h b/include/linux/led-class-rgb.h new file mode 100644 index 000000000000..b5bc607a6b25 --- /dev/null +++ b/include/linux/led-class-rgb.h @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0 +/* LED RGB class interface + * Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/ + */ + +#ifndef __LINUX_RGB_LEDS_H_INCLUDED +#define __LINUX_RGB_LEDS_H_INCLUDED + +#include <linux/leds.h> + +struct led_classdev_rgb; + +#define LED_RGB_RED BIT(0) +#define LED_RGB_GREEN BIT(1) +#define LED_RGB_BLUE BIT(2) +#define LED_RGB_WHITE BIT(3) +#define LED_RGB_ALL (LED_RGB_RED | LED_RGB_GREEN | LED_RGB_BLUE | \ + LED_RGB_WHITE) + +#define LED_RGB_SYSFS_GROUPS_SIZE 5 + +struct led_rgb_ops { + /* set RGB color */ + int (*set_color)(struct led_classdev_rgb *rgbled_cdev); + /* get RGB color */ + int (*get_color)(struct led_classdev_rgb *rgbled_cdev, u32 *color); + + /* set Red color */ + int (*set_red_brightness)(struct led_classdev_rgb *rgbled_cdev, + enum led_brightness brightness); + enum led_brightness (*get_red_brightness)(struct led_classdev_rgb *rgbled_cdev); + /* set green color */ + int (*set_green_brightness)(struct led_classdev_rgb *rgbled_cdev, + enum led_brightness brightness); + enum led_brightness (*get_green_brightness)(struct led_classdev_rgb *rgbled_cdev); + + /* set blue color */ + int (*set_blue_brightness)(struct led_classdev_rgb *rgbled_cdev, + enum led_brightness brightness); + enum led_brightness (*get_blue_brightness)(struct led_classdev_rgb *rgbled_cdev); + +}; + +struct led_rgb_colors { + u8 red; + u8 green; + u8 blue; +}; + +struct led_classdev_rgb { + /* led class device */ + struct led_classdev led_cdev; + + /* rgb led specific ops */ + struct led_rgb_ops *ops; + + /* RGB colors to set intensity per LED */ + struct led_rgb_colors rgb_colors; + + const struct attribute_group *sysfs_groups[LED_RGB_SYSFS_GROUPS_SIZE]; +}; + +static inline struct led_classdev_rgb *lcdev_to_rgbcdev( + struct led_classdev *lcdev) +{ + return container_of(lcdev, struct led_classdev_rgb, led_cdev); +} + +/** + * led_classdev_rgb_register - register a new object of led_classdev class + * with support for rgb LEDs + * @parent: the rgb LED to register + * @fled_cdev: the led_classdev_rgb structure for this device + * + * Returns: 0 on success or negative error value on failure + */ +extern int led_classdev_rgb_register(struct device *parent, + struct led_classdev_rgb *rgbled_cdev); + +/** + * led_classdev_rgb_unregister - unregisters an object of led_classdev class + * with support for rgb LEDs + * @rgbled_cdev: the rgb LED to unregister + * + * Unregister a previously registered via led_classdev_rgb_register object + */ +extern void led_classdev_rgb_unregister(struct led_classdev_rgb *rgbled_cdev); + +#endif /* __LINUX_RGB_LEDS_H_INCLUDED */ -- 2.20.1.98.gecbdaf0899