On Mon 2019-01-21 15:56:55, Dan Murphy wrote: > 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> I'd really like to agree on the interface first, before reviewing implementation. > --- > 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 -- (english) http://www.livejournal.com/~pavelmachek (cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html