Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

leds: core: Add support for composing LED class device names

Add generic support for composing LED class device name. The newly
introduced led_compose_name() function composes device name according
to either <color:function> or <devicename:color:function> pattern,
depending on the configuration of initialization data.

Backward compatibility with in-driver hard-coded LED class device
names is assured thanks to the default_label and devicename properties
of newly introduced struct led_init_data.

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

At the occassion of amending the Documentation/leds/leds-class.txt
unify spelling: colour -> color.

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 using vendor or product name for devicename part
of LED name doesn't convey any added value since that information had been
already available in sysfs. The script performs also basic validation
of a LED class device name.

Signed-off-by: Jacek Anaszewski <jacek.anaszewski@gmail.com>
Cc: Baolin Wang <baolin.wang@linaro.org>
Cc: Dan Murphy <dmurphy@ti.com>
Cc: Daniel Mack <daniel@zonque.org>
Cc: Linus Walleij <linus.walleij@linaro.org>
Cc: Oleh Kravchenko <oleg@kaa.org.ua>
Cc: Sakari Ailus <sakari.ailus@linux.intel.com>
Cc: Simon Shields <simon@lineageos.org>
Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
Acked-by: Pavel Machek <pavel@ucw.cz>

+457 -5
+66 -2
Documentation/leds/leds-class.rst
··· 43 43 44 44 Is currently of the form: 45 45 46 - "devicename:colour:function" 46 + "devicename:color:function" 47 47 48 - There have been calls for LED properties such as colour to be exported as 48 + - devicename: 49 + it should refer to a unique identifier created by the kernel, 50 + like e.g. phyN for network devices or inputN for input devices, rather 51 + than to the hardware; the information related to the product and the bus 52 + to which given device is hooked is available in sysfs and can be 53 + retrieved using get_led_device_info.sh script from tools/leds; generally 54 + this section is expected mostly for LEDs that are somehow associated with 55 + other devices. 56 + 57 + - color: 58 + one of LED_COLOR_ID_* definitions from the header 59 + include/dt-bindings/leds/common.h. 60 + 61 + - function: 62 + one of LED_FUNCTION_* definitions from the header 63 + include/dt-bindings/leds/common.h. 64 + 65 + If required color or function is missing, please submit a patch 66 + to linux-leds@vger.kernel.org. 67 + 68 + It is possible that more than one LED with the same color and function will 69 + be required for given platform, differing only with an ordinal number. 70 + In this case it is preferable to just concatenate the predefined LED_FUNCTION_* 71 + name with required "-N" suffix in the driver. fwnode based drivers can use 72 + function-enumerator property for that and then the concatenation will be handled 73 + automatically by the LED core upon LED class device registration. 74 + 75 + LED subsystem has also a protection against name clash, that may occur 76 + when LED class device is created by a driver of hot-pluggable device and 77 + it doesn't provide unique devicename section. In this case numerical 78 + suffix (e.g. "_1", "_2", "_3" etc.) is added to the requested LED class 79 + device name. 80 + 81 + There might be still LED class drivers around using vendor or product name 82 + for devicename, but this approach is now deprecated as it doesn't convey 83 + any added value. Product information can be found in other places in sysfs 84 + (see tools/leds/get_led_device_info.sh). 85 + 86 + Examples of proper LED names: 87 + 88 + - "red:disk" 89 + - "white:flash" 90 + - "red:indicator" 91 + - "phy1:green:wlan" 92 + - "phy3::wlan" 93 + - ":kbd_backlight" 94 + - "input5::kbd_backlight" 95 + - "input3::numlock" 96 + - "input3::scrolllock" 97 + - "input3::capslock" 98 + - "mmc1::status" 99 + - "white:status" 100 + 101 + get_led_device_info.sh script can be used for verifying if the LED name 102 + meets the requirements pointed out here. It performs validation of the LED class 103 + devicename sections and gives hints on expected value for a section in case 104 + the validation fails for it. So far the script supports validation 105 + of associations between LEDs and following types of devices: 106 + 107 + - input devices 108 + - ieee80211 compliant USB devices 109 + 110 + The script is open to extensions. 111 + 112 + There have been calls for LED properties such as color to be exported as 49 113 individual led class attributes. As a solution which doesn't incur as much 50 114 overhead, I suggest these become part of the device name. The naming scheme 51 115 above leaves scope for further attributes should they be needed. If sections
+17 -3
drivers/leds/led-class.c
··· 254 254 struct led_classdev *led_cdev, 255 255 struct led_init_data *init_data) 256 256 { 257 - char name[LED_MAX_NAME_SIZE]; 257 + char composed_name[LED_MAX_NAME_SIZE]; 258 + char final_name[LED_MAX_NAME_SIZE]; 259 + const char *proposed_name = composed_name; 258 260 int ret; 259 261 260 - ret = led_classdev_next_name(led_cdev->name, name, sizeof(name)); 262 + if (init_data) { 263 + if (init_data->devname_mandatory && !init_data->devicename) { 264 + dev_err(parent, "Mandatory device name is missing"); 265 + return -EINVAL; 266 + } 267 + ret = led_compose_name(parent, init_data, composed_name); 268 + if (ret < 0) 269 + return ret; 270 + } else { 271 + proposed_name = led_cdev->name; 272 + } 273 + 274 + ret = led_classdev_next_name(proposed_name, final_name, sizeof(final_name)); 261 275 if (ret < 0) 262 276 return ret; 263 277 264 278 mutex_init(&led_cdev->led_access); 265 279 mutex_lock(&led_cdev->led_access); 266 280 led_cdev->dev = device_create_with_groups(leds_class, parent, 0, 267 - led_cdev, led_cdev->groups, "%s", name); 281 + led_cdev, led_cdev->groups, "%s", final_name); 268 282 if (IS_ERR(led_cdev->dev)) { 269 283 mutex_unlock(&led_cdev->led_access); 270 284 return PTR_ERR(led_cdev->dev);
+127
drivers/leds/led-core.c
··· 13 13 #include <linux/module.h> 14 14 #include <linux/mutex.h> 15 15 #include <linux/of.h> 16 + #include <linux/property.h> 16 17 #include <linux/rwsem.h> 17 18 #include <linux/slab.h> 19 + #include <uapi/linux/uleds.h> 18 20 #include "leds.h" 19 21 20 22 DECLARE_RWSEM(leds_list_lock); ··· 24 22 25 23 LIST_HEAD(leds_list); 26 24 EXPORT_SYMBOL_GPL(leds_list); 25 + 26 + const char * const led_colors[LED_COLOR_ID_MAX] = { 27 + [LED_COLOR_ID_WHITE] = "white", 28 + [LED_COLOR_ID_RED] = "red", 29 + [LED_COLOR_ID_GREEN] = "green", 30 + [LED_COLOR_ID_BLUE] = "blue", 31 + [LED_COLOR_ID_AMBER] = "amber", 32 + [LED_COLOR_ID_VIOLET] = "violet", 33 + [LED_COLOR_ID_YELLOW] = "yellow", 34 + [LED_COLOR_ID_IR] = "ir", 35 + }; 36 + EXPORT_SYMBOL_GPL(led_colors); 27 37 28 38 static int __led_set_brightness(struct led_classdev *led_cdev, 29 39 enum led_brightness value) ··· 367 353 led_cdev->flags &= ~LED_SYSFS_DISABLE; 368 354 } 369 355 EXPORT_SYMBOL_GPL(led_sysfs_enable); 356 + 357 + static void led_parse_fwnode_props(struct device *dev, 358 + struct fwnode_handle *fwnode, 359 + struct led_properties *props) 360 + { 361 + int ret; 362 + 363 + if (!fwnode) 364 + return; 365 + 366 + if (fwnode_property_present(fwnode, "label")) { 367 + ret = fwnode_property_read_string(fwnode, "label", &props->label); 368 + if (ret) 369 + dev_err(dev, "Error parsing 'label' property (%d)\n", ret); 370 + return; 371 + } 372 + 373 + if (fwnode_property_present(fwnode, "color")) { 374 + ret = fwnode_property_read_u32(fwnode, "color", &props->color); 375 + if (ret) 376 + dev_err(dev, "Error parsing 'color' property (%d)\n", ret); 377 + else if (props->color >= LED_COLOR_ID_MAX) 378 + dev_err(dev, "LED color identifier out of range\n"); 379 + else 380 + props->color_present = true; 381 + } 382 + 383 + 384 + if (!fwnode_property_present(fwnode, "function")) 385 + return; 386 + 387 + ret = fwnode_property_read_string(fwnode, "function", &props->function); 388 + if (ret) { 389 + dev_err(dev, 390 + "Error parsing 'function' property (%d)\n", 391 + ret); 392 + } 393 + 394 + if (!fwnode_property_present(fwnode, "function-enumerator")) 395 + return; 396 + 397 + ret = fwnode_property_read_u32(fwnode, "function-enumerator", 398 + &props->func_enum); 399 + if (ret) { 400 + dev_err(dev, 401 + "Error parsing 'function-enumerator' property (%d)\n", 402 + ret); 403 + } else { 404 + props->func_enum_present = true; 405 + } 406 + } 407 + 408 + int led_compose_name(struct device *dev, struct led_init_data *init_data, 409 + char *led_classdev_name) 410 + { 411 + struct led_properties props = {}; 412 + struct fwnode_handle *fwnode = init_data->fwnode; 413 + const char *devicename = init_data->devicename; 414 + 415 + if (!led_classdev_name) 416 + return -EINVAL; 417 + 418 + led_parse_fwnode_props(dev, fwnode, &props); 419 + 420 + if (props.label) { 421 + /* 422 + * If init_data.devicename is NULL, then it indicates that 423 + * DT label should be used as-is for LED class device name. 424 + * Otherwise the label is prepended with devicename to compose 425 + * the final LED class device name. 426 + */ 427 + if (!devicename) { 428 + strscpy(led_classdev_name, props.label, 429 + LED_MAX_NAME_SIZE); 430 + } else { 431 + snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s", 432 + devicename, props.label); 433 + } 434 + } else if (props.function || props.color_present) { 435 + char tmp_buf[LED_MAX_NAME_SIZE]; 436 + 437 + if (props.func_enum_present) { 438 + snprintf(tmp_buf, LED_MAX_NAME_SIZE, "%s:%s-%d", 439 + props.color_present ? led_colors[props.color] : "", 440 + props.function ?: "", props.func_enum); 441 + } else { 442 + snprintf(tmp_buf, LED_MAX_NAME_SIZE, "%s:%s", 443 + props.color_present ? led_colors[props.color] : "", 444 + props.function ?: ""); 445 + } 446 + if (init_data->devname_mandatory) { 447 + snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s", 448 + devicename, tmp_buf); 449 + } else { 450 + strscpy(led_classdev_name, tmp_buf, LED_MAX_NAME_SIZE); 451 + 452 + } 453 + } else if (init_data->default_label) { 454 + if (!devicename) { 455 + dev_err(dev, "Legacy LED naming requires devicename segment"); 456 + return -EINVAL; 457 + } 458 + snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s", 459 + devicename, init_data->default_label); 460 + } else if (is_of_node(fwnode)) { 461 + strscpy(led_classdev_name, to_of_node(fwnode)->name, 462 + LED_MAX_NAME_SIZE); 463 + } else 464 + return -EINVAL; 465 + 466 + return 0; 467 + } 468 + EXPORT_SYMBOL_GPL(led_compose_name);
+1
drivers/leds/leds.h
··· 27 27 extern struct rw_semaphore leds_list_lock; 28 28 extern struct list_head leds_list; 29 29 extern struct list_head trigger_list; 30 + extern const char * const led_colors[LED_COLOR_ID_MAX]; 30 31 31 32 #endif /* __LEDS_H_INCLUDED */
+45
include/linux/leds.h
··· 8 8 #ifndef __LINUX_LEDS_H_INCLUDED 9 9 #define __LINUX_LEDS_H_INCLUDED 10 10 11 + #include <dt-bindings/leds/common.h> 11 12 #include <linux/device.h> 12 13 #include <linux/kernfs.h> 13 14 #include <linux/list.h> ··· 34 33 struct led_init_data { 35 34 /* device fwnode handle */ 36 35 struct fwnode_handle *fwnode; 36 + /* 37 + * default <color:function> tuple, for backward compatibility 38 + * with in-driver hard-coded LED names used as a fallback when 39 + * DT "label" property is absent; it should be set to NULL 40 + * in new LED class drivers. 41 + */ 42 + const char *default_label; 43 + /* 44 + * string to be used for devicename section of LED class device 45 + * either for label based LED name composition path or for fwnode 46 + * based when devname_mandatory is true 47 + */ 48 + const char *devicename; 49 + /* 50 + * indicates if LED name should always comprise devicename section; 51 + * only LEDs exposed by drivers of hot-pluggable devices should 52 + * set it to true 53 + */ 54 + bool devname_mandatory; 37 55 }; 38 56 39 57 struct led_classdev { ··· 278 258 extern void led_sysfs_enable(struct led_classdev *led_cdev); 279 259 280 260 /** 261 + * led_compose_name - compose LED class device name 262 + * @dev: LED controller device object 263 + * @child: child fwnode_handle describing a LED or a group of synchronized LEDs; 264 + * it must be provided only for fwnode based LEDs 265 + * @led_classdev_name: composed LED class device name 266 + * 267 + * Create LED class device name basing on the provided init_data argument. 268 + * The name can have <devicename:color:function> or <color:function>. 269 + * form, depending on the init_data configuration. 270 + * 271 + * Returns: 0 on success or negative error value on failure 272 + */ 273 + extern int led_compose_name(struct device *dev, struct led_init_data *init_data, 274 + char *led_classdev_name); 275 + 276 + /** 281 277 * led_sysfs_is_disabled - check if LED sysfs interface is disabled 282 278 * @led_cdev: the LED to query 283 279 * ··· 468 432 struct led_platform_data { 469 433 int num_leds; 470 434 struct led_info *leds; 435 + }; 436 + 437 + struct led_properties { 438 + u32 color; 439 + bool color_present; 440 + const char *function; 441 + u32 func_enum; 442 + bool func_enum_present; 443 + const char *label; 471 444 }; 472 445 473 446 struct gpio_desc;
+201
tools/leds/get_led_device_info.sh
··· 1 + #!/bin/sh 2 + # SPDX-License-Identifier: GPL-2.0 3 + 4 + led_common_defs_path="include/dt-bindings/leds/common.h" 5 + 6 + num_args=$# 7 + if [ $num_args -eq 1 ]; then 8 + linux_top=$(dirname `realpath $0` | awk -F/ \ 9 + '{ \ 10 + i=1; \ 11 + while (i <= NF - 2) { \ 12 + printf $i"/"; \ 13 + i++; \ 14 + }; \ 15 + }') 16 + led_defs_path=$linux_top/$led_common_defs_path 17 + elif [ $num_args -eq 2 ]; then 18 + led_defs_path=`realpath $2` 19 + else 20 + echo "Usage: get_led_device_info.sh LED_CDEV_PATH [LED_COMMON_DEFS_PATH]" 21 + exit 1 22 + fi 23 + 24 + if [ ! -f $led_defs_path ]; then 25 + echo "$led_defs_path doesn't exist" 26 + exit 1 27 + fi 28 + 29 + led_cdev_path=`echo $1 | sed s'/\/$//'` 30 + 31 + ls "$led_cdev_path/brightness" > /dev/null 2>&1 32 + if [ $? -ne 0 ]; then 33 + echo "Device \"$led_cdev_path\" does not exist." 34 + exit 1 35 + fi 36 + 37 + bus=`readlink $led_cdev_path/device/subsystem | sed s'/.*\///'` 38 + usb_subdev=`readlink $led_cdev_path | grep usb | sed s'/\(.*usb[0-9]*\/[0-9]*-[0-9]*\)\/.*/\1/'` 39 + ls "$led_cdev_path/device/of_node/compatible" > /dev/null 2>&1 40 + of_node_missing=$? 41 + 42 + if [ "$bus" = "input" ]; then 43 + input_node=`readlink $led_cdev_path/device | sed s'/.*\///'` 44 + if [ ! -z "$usb_subdev" ]; then 45 + bus="usb" 46 + fi 47 + fi 48 + 49 + if [ "$bus" = "usb" ]; then 50 + usb_interface=`readlink $led_cdev_path | sed s'/.*\(usb[0-9]*\)/\1/' | cut -d\/ -f3` 51 + cd $led_cdev_path/../$usb_subdev 52 + driver=`readlink $usb_interface/driver | sed s'/.*\///'` 53 + if [ -d "$usb_interface/ieee80211" ]; then 54 + wifi_phy=`ls -l $usb_interface/ieee80211 | grep phy | awk '{print $9}'` 55 + fi 56 + idVendor=`cat idVendor` 57 + idProduct=`cat idProduct` 58 + manufacturer=`cat manufacturer` 59 + product=`cat product` 60 + elif [ "$bus" = "input" ]; then 61 + cd $led_cdev_path 62 + product=`cat device/name` 63 + driver=`cat device/device/driver/description` 64 + elif [ $of_node_missing -eq 0 ]; then 65 + cd $led_cdev_path 66 + compatible=`cat device/of_node/compatible` 67 + if [ "$compatible" = "gpio-leds" ]; then 68 + driver="leds-gpio" 69 + elif [ "$compatible" = "pwm-leds" ]; then 70 + driver="leds-pwm" 71 + else 72 + manufacturer=`echo $compatible | awk -F, '{print $1}'` 73 + product=`echo $compatible | awk -F, '{print $2}'` 74 + fi 75 + else 76 + echo "Unknown device type." 77 + exit 1 78 + fi 79 + 80 + printf "\n#####################################\n" 81 + printf "# LED class device hardware details #\n" 82 + printf "#####################################\n\n" 83 + 84 + printf "bus:\t\t\t$bus\n" 85 + 86 + if [ ! -z "$idVendor" ]; then 87 + printf "idVendor:\t\t$idVendor\n" 88 + fi 89 + 90 + if [ ! -z "$idProduct" ]; then 91 + printf "idProduct:\t\t$idProduct\n" 92 + fi 93 + 94 + if [ ! -z "$manufacturer" ]; then 95 + printf "manufacturer:\t\t$manufacturer\n" 96 + fi 97 + 98 + if [ ! -z "$product" ]; then 99 + printf "product:\t\t$product\n" 100 + fi 101 + 102 + if [ ! -z "$driver" ]; then 103 + printf "driver:\t\t\t$driver\n" 104 + fi 105 + 106 + if [ ! -z "$input_node" ]; then 107 + printf "associated input node:\t$input_node\n" 108 + fi 109 + 110 + printf "\n####################################\n" 111 + printf "# LED class device name validation #\n" 112 + printf "####################################\n\n" 113 + 114 + led_name=`echo $led_cdev_path | sed s'/.*\///'` 115 + 116 + num_sections=`echo $led_name | awk -F: '{print NF}'` 117 + 118 + if [ $num_sections -eq 1 ]; then 119 + printf "\":\" delimiter not detected.\t[ FAILED ]\n" 120 + exit 1 121 + elif [ $num_sections -eq 2 ]; then 122 + color=`echo $led_name | cut -d: -f1` 123 + function=`echo $led_name | cut -d: -f2` 124 + elif [ $num_sections -eq 3 ]; then 125 + devicename=`echo $led_name | cut -d: -f1` 126 + color=`echo $led_name | cut -d: -f2` 127 + function=`echo $led_name | cut -d: -f3` 128 + else 129 + printf "Detected %d sections in the LED class device name - should the script be updated?\n" $num_sections 130 + exit 1 131 + fi 132 + 133 + S_DEV="devicename" 134 + S_CLR="color " 135 + S_FUN="function " 136 + status_tab=20 137 + 138 + print_msg_ok() 139 + { 140 + local section_name="$1" 141 + local section_val="$2" 142 + local msg="$3" 143 + printf "$section_name :\t%-${status_tab}.${status_tab}s %s %s\n" "$section_val" "[ OK ] " "$msg" 144 + } 145 + 146 + print_msg_failed() 147 + { 148 + local section_name="$1" 149 + local section_val="$2" 150 + local msg="$3" 151 + printf "$section_name :\t%-${status_tab}.${status_tab}s %s %s\n" "$section_val" "[ FAILED ]" "$msg" 152 + } 153 + 154 + if [ ! -z "$input_node" ]; then 155 + expected_devname=$input_node 156 + elif [ ! -z "$wifi_phy" ]; then 157 + expected_devname=$wifi_phy 158 + fi 159 + 160 + if [ ! -z "$devicename" ]; then 161 + if [ ! -z "$expected_devname" ]; then 162 + if [ "$devicename" = "$expected_devname" ]; then 163 + print_msg_ok "$S_DEV" "$devicename" 164 + else 165 + print_msg_failed "$S_DEV" "$devicename" "Expected: $expected_devname" 166 + fi 167 + else 168 + if [ "$devicename" = "$manufacturer" ]; then 169 + print_msg_failed "$S_DEV" "$devicename" "Redundant: use of vendor name is discouraged" 170 + elif [ "$devicename" = "$product" ]; then 171 + print_msg_failed "$S_DEV" "$devicename" "Redundant: use of product name is discouraged" 172 + else 173 + print_msg_failed "$S_DEV" "$devicename" "Unknown devicename - should the script be updated?" 174 + fi 175 + fi 176 + elif [ ! -z "$expected_devname" ]; then 177 + print_msg_failed "$S_DEV" "blank" "Expected: $expected_devname" 178 + fi 179 + 180 + if [ ! -z "$color" ]; then 181 + color_upper=`echo $color | tr [:lower:] [:upper:]` 182 + color_id_definition=$(cat $led_defs_path | grep "_$color_upper\s" | awk '{print $2}') 183 + if [ ! -z "$color_id_definition" ]; then 184 + print_msg_ok "$S_CLR" "$color" "Matching definition: $color_id_definition" 185 + else 186 + print_msg_failed "$S_CLR" "$color" "Definition not found in $led_defs_path" 187 + fi 188 + 189 + fi 190 + 191 + if [ ! -z "$function" ]; then 192 + # strip optional enumerator 193 + function=`echo $function | sed s'/\(.*\)-[0-9]*$/\1/'` 194 + fun_definition=$(cat $led_defs_path | grep "\"$function\"" | awk '{print $2}') 195 + if [ ! -z "$fun_definition" ]; then 196 + print_msg_ok "$S_FUN" "$function" "Matching definition: $fun_definition" 197 + else 198 + print_msg_failed "$S_FUN" "$function" "Definition not found in $led_defs_path" 199 + fi 200 + 201 + fi