[PATCH 02/25] leds: core: Add support for composing LED class device names

From: Jacek Anaszewski
Date: Sun Mar 10 2019 - 14:29:10 EST


Add public led_compose_name() API for composing LED class device
name basing on fwnode_handle data. The function composes device name
according to either a new <color:function> pattern or the legacy
<devicename:color:function> pattern. The decision on using the
particular pattern is made basing on whether fwnode contains new
"function" and "color" properties, or the legacy "label" proeprty.

Backwards compatibility with in-driver hard-coded LED class device
names is assured thanks to the default_desc argument.

In case none of the aforementioned properties was found, then, for OF
nodes, the node name is adopted for LED class device name.

Alongside these changes added is a new tool - tools/leds/get_led_device_info.sh.
The tool allows retrieving details of a LED class device's parent device,
which proves that getting rid of a devicename section from LED name pattern
is justified since this information is already available in sysfs.

Signed-off-by: Jacek Anaszewski <jacek.anaszewski@xxxxxxxxx>
Cc: Baolin Wang <baolin.wang@xxxxxxxxxx>
Cc: Daniel Mack <daniel@xxxxxxxxxx>
Cc: Dan Murphy <dmurphy@xxxxxx>
Cc: Linus Walleij <linus.walleij@xxxxxxxxxx>
Cc: Oleh Kravchenko <oleg@xxxxxxxxxx>
Cc: Sakari Ailus <sakari.ailus@xxxxxxxxxxxxxxx>
---
Documentation/leds/leds-class.txt | 20 +++++++++-
drivers/leds/led-core.c | 82 +++++++++++++++++++++++++++++++++++++++
include/linux/leds.h | 31 +++++++++++++++
tools/leds/get_led_device_info.sh | 81 ++++++++++++++++++++++++++++++++++++++
4 files changed, 213 insertions(+), 1 deletion(-)
create mode 100755 tools/leds/get_led_device_info.sh

diff --git a/Documentation/leds/leds-class.txt b/Documentation/leds/leds-class.txt
index 8b39cc6b03ee..866fe87063d4 100644
--- a/Documentation/leds/leds-class.txt
+++ b/Documentation/leds/leds-class.txt
@@ -43,7 +43,22 @@ LED Device Naming

Is currently of the form:

-"devicename:colour:function"
+"colour:function"
+
+There might be still LED class drivers around using "devicename:colour:function"
+naming pattern, but the "devicename" section is now deprecated since it used
+to convey information that was already available in the sysfs, like product
+name. There is a tool (tools/leds/get_led_device_info.sh) available for
+retrieving that information per a LED class device.
+
+Associations with other devices, like network ones, should be defined
+via LED triggr mechanism. This approach is applied by some of wireless
+network drivers that create triggers dynamically and incorporate phy
+name into its name. On the other hand input subsystem offers LED - input
+bridge (drivers/input/input-leds.c) for exposing keyboard LEDs as LED class
+devices. The get_led_device_info.sh script has support for retrieving related
+input device node name. Should it support discovery of associations between
+LEDs and other subsystems, please don't hesitate to submit a relevant patch.

There have been calls for LED properties such as colour to be exported as
individual led class attributes. As a solution which doesn't incur as much
@@ -51,6 +66,9 @@ overhead, I suggest these become part of the device name. The naming scheme
above leaves scope for further attributes should they be needed. If sections
of the name don't apply, just leave that section blank.

+Please also keep in mind that LED subsystem has a protection against LED name
+conflict. It adds numerical suffix (e.g. "_1", "_2", "_3" etc.) to the requested
+LED class device name in case it is already in use.

Brightness setting API
======================
diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c
index ede4fa0ac2cc..bad92250d1d5 100644
--- a/drivers/leds/led-core.c
+++ b/drivers/leds/led-core.c
@@ -16,6 +16,8 @@
#include <linux/list.h>
#include <linux/module.h>
#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/property.h>
#include <linux/rwsem.h>
#include "leds.h"

@@ -327,3 +329,83 @@ void led_sysfs_enable(struct led_classdev *led_cdev)
led_cdev->flags &= ~LED_SYSFS_DISABLE;
}
EXPORT_SYMBOL_GPL(led_sysfs_enable);
+
+static void led_parse_properties(struct fwnode_handle *fwnode,
+ struct led_properties *props)
+{
+ int ret;
+
+ if (!fwnode)
+ return;
+
+ if (fwnode_property_present(fwnode, "label")) {
+ ret = fwnode_property_read_string(fwnode, "label", &props->label);
+ if (ret)
+ pr_err("Error parsing \'label\' property (%d)\n", ret);
+ return;
+ }
+
+ if (fwnode_property_present(fwnode, "function")) {
+ ret = fwnode_property_read_string(fwnode, "function", &props->function);
+ if (ret)
+ pr_err("Error parsing \'function\' property (%d)\n", ret);
+ } else {
+ pr_info("\'function\' property not found\n");
+ }
+
+ if (fwnode_property_present(fwnode, "color")) {
+ ret = fwnode_property_read_string(fwnode, "color", &props->color);
+ if (ret)
+ pr_info("Error parsing \'color\' property (%d)\n", ret);
+ } else {
+ pr_info("\'color\' property not found\n");
+ }
+}
+
+int led_compose_name(struct fwnode_handle *fwnode, const char *led_hw_name,
+ const char *default_desc, char *led_classdev_name)
+{
+ struct led_properties props = {};
+
+ if (!led_classdev_name)
+ return -EINVAL;
+
+ led_parse_properties(fwnode, &props);
+
+ if (props.label) {
+ /*
+ * Presence of 'label' DT property implies legacy LED name,
+ * formatted as <devicename:color:function>, with possible
+ * section omission if doesn't apply to given device.
+ *
+ * If no led_hw_name has been passed, then it indicates that
+ * DT label should be used as-is for LED class device name.
+ * Otherwise the label is prepended with led_hw_name to compose
+ * the final LED class device name.
+ */
+ if (!led_hw_name) {
+ strncpy(led_classdev_name, props.label,
+ LED_MAX_NAME_SIZE);
+ } else {
+ snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s",
+ led_hw_name, props.label);
+ }
+ } else if (props.function || props.color) {
+ snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s",
+ props.color ?: "", props.function ?: "");
+ } else if (default_desc) {
+ if (!led_hw_name) {
+ pr_err("Legacy LED naming requires devicename segment");
+ return -EINVAL;
+ }
+ snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s",
+ led_hw_name, default_desc);
+ } else if (is_of_node(fwnode)) {
+ strncpy(led_classdev_name, to_of_node(fwnode)->name,
+ LED_MAX_NAME_SIZE);
+ } else
+ return -EINVAL;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(led_compose_name);
diff --git a/include/linux/leds.h b/include/linux/leds.h
index bffb4315fd66..c2936fc989d4 100644
--- a/include/linux/leds.h
+++ b/include/linux/leds.h
@@ -252,6 +252,31 @@ extern void led_sysfs_disable(struct led_classdev *led_cdev);
extern void led_sysfs_enable(struct led_classdev *led_cdev);

/**
+ * led_compose_name - compose LED class device name
+ * @child: child fwnode_handle describing a LED,
+ * or a group of synchronized LEDs.
+ * @led_hw_name: name of the LED controller, used when falling back to legacy
+ * LED naming; it should be set to NULL in new LED class drivers
+ * @default_desc: default <color:function> tuple, for backwards compatibility
+ * with in-driver hard-coded LED names used as a fallback when
+ * "label" DT property is absent; it should be set to NULL
+ * in new LED class drivers
+ * @led_classdev_name: composed LED class device name
+ *
+ * Create LED class device name basing on the configuration provided by the
+ * board firmware. The name can have a legacy form <devicename:color:function>,
+ * or a new form <color:function>. The latter is chosen if "label" property is
+ * absent and at least one of "color" or "function" is present in the fwnode,
+ * leaving the section blank if the related property is absent. In case none
+ * of the aforementioned properties is found, then, for OF nodes, the node name
+ * is adopted for LED class device name.
+ *
+ * Returns: 0 on success or negative error value on failure
+ */
+extern int led_compose_name(struct fwnode_handle *child, const char *led_hw_name,
+ const char *default_desc, char *led_classdev_name);
+
+/**
* led_sysfs_is_disabled - check if LED sysfs interface is disabled
* @led_cdev: the LED to query
*
@@ -428,6 +453,12 @@ struct led_platform_data {
struct led_info *leds;
};

+struct led_properties {
+ const char *color;
+ const char *function;
+ const char *label;
+};
+
struct gpio_desc;
typedef int (*gpio_blink_set_t)(struct gpio_desc *desc, int state,
unsigned long *delay_on,
diff --git a/tools/leds/get_led_device_info.sh b/tools/leds/get_led_device_info.sh
new file mode 100755
index 000000000000..4671aa690e4a
--- /dev/null
+++ b/tools/leds/get_led_device_info.sh
@@ -0,0 +1,81 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0
+
+if [ $# -ne 1 ]; then
+ echo "get_led_devicename.sh LED_CDEV_PATH"
+ exit 1
+fi
+
+led_cdev_path=`echo $1 | sed s'/\/$//'`
+
+ls "$led_cdev_path/brightness" > /dev/null 2>&1
+if [ $? -ne 0 ]; then
+ echo "Device \"$led_cdev_path\" does not exist."
+ exit 1
+fi
+
+bus=`readlink $led_cdev_path/device/subsystem | sed s'/.*\///'`
+usb_subdev=`readlink $led_cdev_path | grep usb | sed s'/\(.*usb[0-9]*\/[0-9]*-[0-9]*\)\/.*/\1/'`
+ls "$led_cdev_path/device/of_node/compatible" > /dev/null 2>&1
+of_node_missing=$?
+
+if [ "$bus" = "input" ]; then
+ input_node=`readlink $led_cdev_path/device | sed s'/.*\///'`
+ if [ ! -z $usb_subdev ]; then
+ bus="usb"
+ fi
+fi
+
+if [ "$bus" = "usb" ]; then
+ usb_interface=`readlink $led_cdev_path | sed s'/.*\(usb[0-9]*\)/\1/' | cut -d \/ -f 3`
+ driver=`readlink $usb_interface/driver | sed s'/.*\///'`
+ cd $led_cdev_path/../$usb_subdev
+ idVendor=`cat idVendor`
+ idProduct=`cat idProduct`
+ manufacturer=`cat manufacturer`
+ product=`cat product`
+elif [ "$bus" = "input" ]; then
+ cd $led_cdev_path
+ product=`cat device/name`
+ driver=`cat device/device/driver/description`
+elif [ $of_node_missing -eq 0 ]; then
+ cd $led_cdev_path
+ compatible=`cat device/of_node/compatible`
+ if [ "$compatible" = "gpio-leds" ]; then
+ driver="leds-gpio"
+ elif [ "$compatible" = "pwm-leds" ]; then
+ driver="leds-pwm"
+ else
+ manufacturer=`echo $compatible | cut -d, -f1`
+ product=`echo $compatible | cut -d, -f2`
+ fi
+else
+ echo "Unknown device type."
+ exit 1
+fi
+
+printf "bus:\t\t\t$bus\n"
+
+if [ ! -z "$idVendor" ]; then
+ printf "idVendor:\t\t$idVendor\n"
+fi
+
+if [ ! -z "$idProduct" ]; then
+ printf "idProduct:\t\t$idProduct\n"
+fi
+
+if [ ! -z "$manufacturer" ]; then
+ printf "manufacturer:\t\t$manufacturer\n"
+fi
+
+if [ ! -z "$product" ]; then
+ printf "product:\t\t$product\n"
+fi
+
+if [ ! -z "$driver" ]; then
+ printf "driver:\t\t\t$driver\n"
+fi
+
+if [ ! -z "$input_node" ]; then
+ printf "associated input node:\t$input_node\n"
+fi
--
2.11.0