From patchwork Mon Jan 21 21:56:55 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dan Murphy X-Patchwork-Id: 156256 Delivered-To: patch@linaro.org Received: by 2002:a02:48:0:0:0:0:0 with SMTP id 69csp6848713jaa; Mon, 21 Jan 2019 13:57:10 -0800 (PST) X-Google-Smtp-Source: ALg8bN6ha1zaZT+bSuE7J9tn0YPFEeh0qms8WMRNBl+cfKpRhEfNmFOKgNh1RtoGSgxqoRAQuTCq X-Received: by 2002:a62:fc52:: with SMTP id e79mr31598097pfh.8.1548107829953; Mon, 21 Jan 2019 13:57:09 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1548107829; cv=none; d=google.com; s=arc-20160816; b=yjHa0jagEQhnkCGzRkPuqrBWrPtx8wflO1HQ1igg7bH0nZkotIKm8BwhAIApVlZbDg YdjNvqtPt7AAz7ON/lI+q0+e0IPb5BUzM+PuqHAWClx4OaccjgbVqp6aRjHMlGr66x3c jRthrksK73Mq3JVZuAfpCCJYaag32t/BrYGxMbXWNlUOd7yLq7rlQNhqGetk1MmZOtk7 5IHiNXS9jO3f8VxKdVdi1VlgLkkIMUwitfckrtBzuQNTQKJ47Ilgf08V+CrubFxvVk/M nQdnGj9CfLyDQun2Ks/yqjNlLcPgzMIUCOhfoHh9wHpQsVucnLVTAEd+60S33bbJ9Z0t vnww== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:dkim-signature; bh=uJqCWvpeCGTDa+/5qaviaWDSzoXdFFvyHIka7+9IYp0=; b=nkto0jTxiXJF8CI4mMW2Nd/4r9X5UQKqAAbYGYmD4ZI/LcHmV8e2M98zFBcwLPWW01 cofwSIesNLkAARW9bdlmmnoeRRWYfhdPnF/w5B7rwsEmhRcPKHl92YV61E7rStRBuLrK 5xhZSZK+jNA8fBx09ygdD9t5fPlmNzli5hbTf3rYzljkQ14E3Tt+D/O83fDPjWCcsyV2 VjAIDVDwxDV1M9XjNIZCnQHjni0r3gHjXOpt2ib7bksNkj/bcUR1QLYkRxr0engFpVQ7 q1ui2O7kk6eXZCf3LrDxgOksEpKN7S4v5GZNbLa2Aq7/Ujvv3+EH3xKnX9iifZ2UQhwf wcAQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@ti.com header.s=ti-com-17Q1 header.b=FccCE0W8; spf=pass (google.com: best guess record for domain of linux-leds-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-leds-owner@vger.kernel.org; dmarc=pass (p=QUARANTINE sp=NONE dis=NONE) header.from=ti.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id 9si14124932plc.40.2019.01.21.13.57.09; Mon, 21 Jan 2019 13:57:09 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-leds-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@ti.com header.s=ti-com-17Q1 header.b=FccCE0W8; spf=pass (google.com: best guess record for domain of linux-leds-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-leds-owner@vger.kernel.org; dmarc=pass (p=QUARANTINE sp=NONE dis=NONE) header.from=ti.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727761AbfAUV5J (ORCPT + 1 other); Mon, 21 Jan 2019 16:57:09 -0500 Received: from lelv0143.ext.ti.com ([198.47.23.248]:58854 "EHLO lelv0143.ext.ti.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727791AbfAUV5I (ORCPT ); Mon, 21 Jan 2019 16:57:08 -0500 Received: from fllv0034.itg.ti.com ([10.64.40.246]) by lelv0143.ext.ti.com (8.15.2/8.15.2) with ESMTP id x0LLv3eC114957; Mon, 21 Jan 2019 15:57:03 -0600 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ti.com; s=ti-com-17Q1; t=1548107823; bh=uJqCWvpeCGTDa+/5qaviaWDSzoXdFFvyHIka7+9IYp0=; h=From:To:CC:Subject:Date:In-Reply-To:References; b=FccCE0W8FZNgDozGWGz5at6lo5/7P/0/hQHLBFEgea3kecnl2dN/fWPIZuFcZHTw8 zJOg/MZxWCHfoAdj8TqnDK1mphuzYRvlew13J886CvCfKltJUA0wee8Vb+cJqmBBRZ YhEENTT90q0nmQ0NrPlPmGj9lbKXh7TjHeBhOoKg= Received: from DFLE115.ent.ti.com (dfle115.ent.ti.com [10.64.6.36]) by fllv0034.itg.ti.com (8.15.2/8.15.2) with ESMTPS id x0LLv3KB125677 (version=TLSv1.2 cipher=AES256-GCM-SHA384 bits=256 verify=FAIL); Mon, 21 Jan 2019 15:57:03 -0600 Received: from DFLE100.ent.ti.com (10.64.6.21) by DFLE115.ent.ti.com (10.64.6.36) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256_P256) id 15.1.1591.10; Mon, 21 Jan 2019 15:57:03 -0600 Received: from dflp33.itg.ti.com (10.64.6.16) by DFLE100.ent.ti.com (10.64.6.21) with Microsoft SMTP Server (version=TLS1_0, cipher=TLS_RSA_WITH_AES_256_CBC_SHA) id 15.1.1591.10 via Frontend Transport; Mon, 21 Jan 2019 15:57:03 -0600 Received: from legion.dal.desgin.ti.com (legion.dal.design.ti.com [128.247.22.53]) by dflp33.itg.ti.com (8.14.3/8.13.8) with ESMTP id x0LLv3fi020797; Mon, 21 Jan 2019 15:57:03 -0600 Received: from localhost (a0272616local-lt.dhcp.ti.com [172.22.78.123]) by legion.dal.desgin.ti.com (8.11.7p1+Sun/8.11.7) with ESMTP id x0LLv2U20728; Mon, 21 Jan 2019 15:57:02 -0600 (CST) From: Dan Murphy To: CC: , , , Dan Murphy Subject: [RFC PATCH 2/6] leds: RGB Framework: Introduce a simple RGB framework Date: Mon, 21 Jan 2019 15:56:55 -0600 Message-ID: <20190121215659.31125-3-dmurphy@ti.com> X-Mailer: git-send-email 2.12.2 In-Reply-To: <20190121215659.31125-1-dmurphy@ti.com> References: <20190121215659.31125-1-dmurphy@ti.com> MIME-Version: 1.0 X-EXCLAIMER-MD-CONFIG: e1e8a2fd-e40a-4ac6-ac9b-f7e9cc9ee180 Sender: linux-leds-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-leds@vger.kernel.org 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 --- 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 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 +#include +#include +#include +#include +#include +#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 "); +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 + +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 */