diff mbox series

[RFC,2/6] leds: RGB Framework: Introduce a simple RGB framework

Message ID 20190121215659.31125-3-dmurphy@ti.com
State New
Headers show
Series Simplified RGB class Version 1 | expand

Commit Message

Dan Murphy Jan. 21, 2019, 9:56 p.m. UTC
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@ti.com>

---
 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

-- 
2.20.1.98.gecbdaf0899

Comments

Pavel Machek Jan. 22, 2019, 12:33 p.m. UTC | #1
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@ti.com>


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@ti.com>");

> +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
Dan Murphy Jan. 22, 2019, 4:14 p.m. UTC | #2
Pavel

On 1/22/19 6:33 AM, Pavel Machek wrote:
> 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@ti.com>

> 

> I'd really like to agree on the interface first, before reviewing

> implementation.

> 

> 


I agree.  I want to be able to have discussions on RFCs and move the discussion off
of device driver submissions.  I think good talking points are getting lost in long unrelated email chains.

Also I wanted to understand the feasibility of the specific interfaces being proposed.
And this is the RGB class so it only collates the LEDs into a single node.
The interfaces exposed are not set in stone just proposed as there is some overlap with Vesa's proposal.

The pwm interfaces seem to be global to all devices but the color interfaces, need to be discussed as I believe
the LED class should assume white LEDs and if there are different colors then maybe provide a multi-color
class that registers and exposes color specific files as well as the base LED class files.

After reviewing some data sheets I am thinking pwm_channels may not be a good generic interface.
And should be marked as an optional interface.

Some devices I looked into do not have pwm channels or pwm control the LP55xx and CPCAP do but the
LP50xx and NCP5623 do not.  We can also discuss devices that their current is set by hardware and can
be enabled/disabled via GPIO like the LTC3212.  Brightness and PWM have no affect on this device.

GPIO LEDs may or may not be connected to a PWM and the current may be set.

It would be good to summarize the current interface discussion here or start a separate RFC.
Indicating what the interface is and if it is mandatory or optional for the driver to expose.

brightness - Mandatory in LED base class

Jacek did comment on the interface names but there was no dissension or agreement.

There are so many proposed interfaces.

Dan

> 

>> ---

>>  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@ti.com>");

>> +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

> 



-- 
------------------
Dan Murphy
diff mbox series

Patch

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@ti.com>");
+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 */