From: Heiner Kallweit <hkallweit1@gmail.com>
To: Jacek Anaszewski <j.anaszewski@samsung.com>
Cc: linux-leds@vger.kernel.org
Subject: [PATCH] leds: core: add support for RGB LED's
Date: Sun, 24 Jan 2016 17:09:17 +0100 [thread overview]
Message-ID: <56A4F72D.8080300@gmail.com> (raw)
This patch adds support for RGB LED's to the core.
To use this extension a driver must add LED_DEV_CAP_RGB to the
led_classdev flags. The callbacks to be implemented by the driver
are the same as before, just enum led_brightness is used as
<00000000><RRRRRRRR><GGGGGGGG><BBBBBBB> now.
A new sysfs property "rgb" allows to set the color from userspace.
The RGB extension is fully compatible with the current
brightness-based API incl. triggers.
What still needs to be done:
update Documentation/leds/leds-class.txt
Signed-off-by: Heiner Kallweit <hkallweit1@gmail.com>
---
drivers/leds/led-class.c | 69 +++++++++++++++++++++++++++
drivers/leds/led-core.c | 121 ++++++++++++++++++++++++++++++++++++++++-------
drivers/leds/leds.h | 16 +++++++
include/linux/leds.h | 3 ++
4 files changed, 193 insertions(+), 16 deletions(-)
diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
index aa84e5b..ed5fc05 100644
--- a/drivers/leds/led-class.c
+++ b/drivers/leds/led-class.c
@@ -64,6 +64,46 @@ unlock:
}
static DEVICE_ATTR_RW(brightness);
+static ssize_t rgb_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+
+ return sprintf(buf, "0x%06x\n", led_get_rgb_val(led_cdev));
+}
+
+static ssize_t rgb_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 state;
+ ssize_t ret;
+
+ mutex_lock(&led_cdev->led_access);
+
+ if (led_sysfs_is_disabled(led_cdev)) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ ret = kstrtoul(buf, 0, &state);
+ if (ret)
+ goto unlock;
+
+ if (state > LED_WHITE) {
+ ret = -EINVAL;
+ goto unlock;
+ }
+
+ led_set_rgb(led_cdev, state);
+
+ ret = size;
+unlock:
+ mutex_unlock(&led_cdev->led_access);
+ return ret;
+}
+static DEVICE_ATTR_RW(rgb);
+
static ssize_t max_brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
@@ -90,10 +130,19 @@ static struct attribute *led_class_attrs[] = {
NULL,
};
+static struct attribute *led_rgb_attrs[] = {
+ &dev_attr_rgb.attr,
+ NULL,
+};
+
static const struct attribute_group led_group = {
.attrs = led_class_attrs,
};
+static const struct attribute_group led_rgb_group = {
+ .attrs = led_rgb_attrs,
+};
+
static const struct attribute_group *led_groups[] = {
&led_group,
#ifdef CONFIG_LEDS_TRIGGERS
@@ -102,6 +151,11 @@ static const struct attribute_group *led_groups[] = {
NULL,
};
+static const struct attribute_group *led_rgb_groups[] = {
+ &led_rgb_group,
+ NULL,
+};
+
/**
* led_classdev_suspend - suspend an led_classdev.
* @led_cdev: the led_classdev to suspend.
@@ -190,6 +244,21 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
char name[64];
int ret;
+ /* FLASH is not supported for RGB LEDs so far
+ * and RGB enforces max_brightness = LED_FULL.
+ * Initialize the color as white.
+ */
+ if (led_is_rgb(led_cdev)) {
+ if (led_cdev->flags & LED_DEV_CAP_FLASH) {
+ dev_err(parent,
+ "RGB and FLASH mode are mutually exclusive\n");
+ return -EINVAL;
+ }
+ led_cdev->max_brightness = LED_FULL;
+ led_cdev->rgb_val = LED_WHITE;
+ led_cdev->groups = led_rgb_groups;
+ }
+
ret = led_classdev_next_name(led_cdev->name, name, sizeof(name));
if (ret < 0)
return ret;
diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c
index ad684b6..522d863 100644
--- a/drivers/leds/led-core.c
+++ b/drivers/leds/led-core.c
@@ -25,6 +25,61 @@ EXPORT_SYMBOL_GPL(leds_list_lock);
LIST_HEAD(leds_list);
EXPORT_SYMBOL_GPL(leds_list);
+struct led_color { u8 red, green, blue; };
+
+static inline struct led_color val_to_led_color(u32 rgb)
+{
+ struct led_color col = {
+ .red = (rgb >> 16) & 0xff,
+ .green = (rgb >> 8) & 0xff,
+ .blue = rgb & 0xff
+ };
+ return col;
+}
+
+static inline u32 led_color_to_val(struct led_color col)
+{
+ return (col.red << 16) + (col.green << 8) + col.blue;
+}
+
+static void scale_led_color(struct led_color *col, u32 val1, u32 val2)
+{
+ col->red = DIV_ROUND_CLOSEST(col->red * val1, val2);
+ col->green = DIV_ROUND_CLOSEST(col->green * val1, val2);
+ col->blue = DIV_ROUND_CLOSEST(col->blue * val1, val2);
+}
+
+static void led_set_rgb_raw(struct led_classdev *cdev, u32 rgb,
+ int update_mode)
+{
+ struct led_color col = val_to_led_color(rgb);
+ u8 max_raw;
+
+ max_raw = max(col.red, col.green);
+ max_raw = max(max_raw, col.blue);
+
+ if (update_mode & LED_UPDATE_BRIGHTNESS)
+ cdev->brightness = max_raw;
+
+ if (update_mode & LED_UPDATE_COLOR) {
+ if (!max_raw)
+ cdev->rgb_val = LED_WHITE;
+ else {
+ scale_led_color(&col, LED_FULL, max_raw);
+ cdev->rgb_val = led_color_to_val(col);
+ }
+ }
+}
+
+static u32 led_get_rgb_raw(struct led_classdev *cdev, u32 rgb, u32 brightness)
+{
+ struct led_color col = val_to_led_color(rgb);
+
+ scale_led_color(&col, brightness, LED_FULL);
+
+ return led_color_to_val(col);
+}
+
static void led_timer_function(unsigned long data)
{
struct led_classdev *led_cdev = (void *)data;
@@ -83,6 +138,7 @@ static void set_brightness_delayed(struct work_struct *ws)
{
struct led_classdev *led_cdev =
container_of(ws, struct led_classdev, set_brightness_work);
+ enum led_brightness new_val;
int ret = 0;
if (led_cdev->flags & LED_BLINK_DISABLE) {
@@ -91,11 +147,16 @@ static void set_brightness_delayed(struct work_struct *ws)
led_cdev->flags &= ~LED_BLINK_DISABLE;
}
+ if (led_is_rgb(led_cdev))
+ new_val = led_get_rgb_raw(led_cdev, led_cdev->delayed_rgb_value,
+ led_cdev->delayed_set_value);
+ else
+ new_val = led_cdev->delayed_set_value;
+
if (led_cdev->brightness_set)
- led_cdev->brightness_set(led_cdev, led_cdev->delayed_set_value);
+ led_cdev->brightness_set(led_cdev, new_val);
else if (led_cdev->brightness_set_blocking)
- ret = led_cdev->brightness_set_blocking(led_cdev,
- led_cdev->delayed_set_value);
+ ret = led_cdev->brightness_set_blocking(led_cdev, new_val);
else
ret = -ENOTSUPP;
if (ret < 0 &&
@@ -232,17 +293,32 @@ void led_set_brightness(struct led_classdev *led_cdev,
}
EXPORT_SYMBOL_GPL(led_set_brightness);
+void led_set_rgb(struct led_classdev *led_cdev, u32 rgb_val)
+{
+ if (!led_is_rgb(led_cdev) || (led_cdev->flags & LED_SUSPENDED))
+ return;
+
+ /* set color only, don't touch brightness */
+ led_set_rgb_raw(led_cdev, rgb_val, LED_UPDATE_COLOR);
+ led_set_brightness_nopm(led_cdev, led_cdev->brightness);
+}
+EXPORT_SYMBOL_GPL(led_set_rgb);
+
void led_set_brightness_nopm(struct led_classdev *led_cdev,
enum led_brightness value)
{
/* Use brightness_set op if available, it is guaranteed not to sleep */
if (led_cdev->brightness_set) {
+ if (led_is_rgb(led_cdev))
+ value = led_get_rgb_raw(led_cdev, led_cdev->rgb_val,
+ value);
led_cdev->brightness_set(led_cdev, value);
return;
}
/* If brightness setting can sleep, delegate it to a work queue task */
led_cdev->delayed_set_value = value;
+ led_cdev->delayed_rgb_value = led_cdev->rgb_val;
schedule_work(&led_cdev->set_brightness_work);
}
EXPORT_SYMBOL_GPL(led_set_brightness_nopm);
@@ -270,26 +346,39 @@ int led_set_brightness_sync(struct led_classdev *led_cdev,
if (led_cdev->flags & LED_SUSPENDED)
return 0;
- if (led_cdev->brightness_set_blocking)
- return led_cdev->brightness_set_blocking(led_cdev,
- led_cdev->brightness);
- return -ENOTSUPP;
+ if (!led_cdev->brightness_set_blocking)
+ return -ENOTSUPP;
+
+ if (led_is_rgb(led_cdev))
+ value = led_get_rgb_raw(led_cdev, led_cdev->rgb_val,
+ led_cdev->brightness);
+ else
+ value = led_cdev->brightness;
+
+ return led_cdev->brightness_set_blocking(led_cdev, value);
}
EXPORT_SYMBOL_GPL(led_set_brightness_sync);
int led_update_brightness(struct led_classdev *led_cdev)
{
- int ret = 0;
+ int ret;
- if (led_cdev->brightness_get) {
- ret = led_cdev->brightness_get(led_cdev);
- if (ret >= 0) {
- led_cdev->brightness = ret;
- return 0;
- }
- }
+ if (!led_cdev->brightness_get)
+ return 0;
+
+ ret = led_cdev->brightness_get(led_cdev);
+ if (ret < 0)
+ return ret;
+
+ if (led_is_rgb(led_cdev))
+ /* Updating the color is not supported because it might
+ * result in biased colors if the brightness is low.
+ */
+ led_set_rgb_raw(led_cdev, ret, LED_UPDATE_BRIGHTNESS);
+ else
+ led_cdev->brightness = ret;
- return ret;
+ return 0;
}
EXPORT_SYMBOL_GPL(led_update_brightness);
diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h
index db3f20d..e037d7e 100644
--- a/drivers/leds/leds.h
+++ b/drivers/leds/leds.h
@@ -16,17 +16,33 @@
#include <linux/rwsem.h>
#include <linux/leds.h>
+#define LED_UPDATE_COLOR BIT(0)
+#define LED_UPDATE_BRIGHTNESS BIT(1)
+
+#define LED_WHITE 0xffffff
+
static inline int led_get_brightness(struct led_classdev *led_cdev)
{
return led_cdev->brightness;
}
+static inline u32 led_get_rgb_val(struct led_classdev *led_cdev)
+{
+ return led_cdev->rgb_val;
+}
+
+static inline bool led_is_rgb(struct led_classdev *led_cdev)
+{
+ return (led_cdev->flags & LED_DEV_CAP_RGB) != 0;
+}
+
void led_init_core(struct led_classdev *led_cdev);
void led_stop_software_blink(struct led_classdev *led_cdev);
void led_set_brightness_nopm(struct led_classdev *led_cdev,
enum led_brightness value);
void led_set_brightness_nosleep(struct led_classdev *led_cdev,
enum led_brightness value);
+void led_set_rgb(struct led_classdev *led_cdev, u32 rgb_val);
extern struct rw_semaphore leds_list_lock;
extern struct list_head leds_list;
diff --git a/include/linux/leds.h b/include/linux/leds.h
index f203a8f..e224b4b 100644
--- a/include/linux/leds.h
+++ b/include/linux/leds.h
@@ -35,6 +35,7 @@ struct led_classdev {
const char *name;
enum led_brightness brightness;
enum led_brightness max_brightness;
+ u32 rgb_val;
int flags;
/* Lower 16 bits reflect status */
@@ -50,6 +51,7 @@ struct led_classdev {
#define LED_SYSFS_DISABLE (1 << 22)
#define LED_DEV_CAP_FLASH (1 << 23)
#define LED_HW_PLUGGABLE (1 << 24)
+#define LED_DEV_CAP_RGB (1 << 25)
/* Set LED brightness level
* Must not sleep. Use brightness_set_blocking for drivers
@@ -91,6 +93,7 @@ struct led_classdev {
struct work_struct set_brightness_work;
int delayed_set_value;
+ u32 delayed_rgb_value;
#ifdef CONFIG_LEDS_TRIGGERS
/* Protects the trigger data below */
--
2.7.0
reply other threads:[~2016-01-24 16:09 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=56A4F72D.8080300@gmail.com \
--to=hkallweit1@gmail.com \
--cc=j.anaszewski@samsung.com \
--cc=linux-leds@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.