Adding support for controlling the leds found on Intel NUC
Subsystems (2)
v2 cover Adding support for controlling the leds found on Intel NUC
This series add support for the LEDs found at Intel NUCs since NUC version 6.
On several NUC models, the function of the LEDs are controlled by the NUC firmware and are programmable, which allow them to indicate several different hardware events.
They can also be programmed to represent an userspace-driven event.
Some models come with single colored or dual-colored LEDs, but high end models have RGB LEDs.
Programming them can ether be done via BIOS or by the OS, however, BIOS settings are limited. So, the vendor offers a Windows application that allows to fully use the functionality provided by the firmware/hardware.
It should be noticed that there are 3 different API types, and there are already some OOT drivers that were written to support them, using procfs, each one using a different (and IMO confusing) API.
After looking at the existing drivers and not liking the uAPI interfaces there, and needed to ajust the LEDs again after BIOS config reset, as this is a recommended procedure after BIOS upgrades, I opted to write a new driver from scratch, unifying support for all different versions and using sysfs via the leds class.
It should be noticed that those devices use the ACPI Windows Management Interface (WMI). There are actually 3 different implementations for it:
- one for NUC6/NUC7, which has limited support for programming just two LEDs;
- a complely re-written interface for NUC8, which can program up to seven LEDs, named version 0.64;
- an extended version of the NUC8 API, added for NUC10, called version 1.0, with has a few differences from version 0.64.
Such WMI APIs are documented at:
- https://www.intel.com/content/www/us/en/support/articles/000023426/intel-nuc/intel-nuc-kits.html
- https://raw.githubusercontent.com/nomego/intel_nuc_led/master/specs/INTEL_WMI_LED_0.64.pdf
- https://www.intel.com/content/dam/support/us/en/documents/intel-nuc/WMI-Spec-Intel-NUC-NUC10ixFNx.pdf
It should be noticed that, I wrote this driver mainly for my NUC8 (NUC8i7HNK), but I also used a NUC6 in order to double-check if NUC6 support was not crashing. Yet, while the NUC6 model I have accepts the WMI LED API, it doesn't really work, as it seems that the BIOS of my NUC6 doesn't let userspace to program the LEDs.
I don't have any devices using NUC10 API, so the few differences between it and NUC8 API weren't tested.
Due to the lack of full tests on NUC6 and NUC10, and because I wrote a new uAPI that's different than the procfs-based ones found at the OOT drivers, IMO, the better would be to merge this via staging, but as Greg's feedback were to apply it directly under drivers/leds, this version was changed considering such premise.
PS. : after having the series accepted, I'll submit an extra patch for Documentation/ABI, summarizing the ABI documentation found on patch 01.
-
- v2:
- Added an ABI documentation at patch 01 and dropped the TODO;
- Removed the .remove function, as it was just printing a message;
- Add a check for a return code, as suggested by Dan Carpenter;
- Did some code cleanups as also suggested by Dan Carpenter;
- Changed the Kconfig description as suggested by Randy Dunlap.
Mauro Carvalho Chehab (17):
docs: describe the API used to set NUC LEDs
leds: add support for NUC WMI LEDs
leds: leds-nuc: detect WMI API detection
leds: leds-nuc: add support for changing S0 brightness
leds: leds-nuc: add all types of brightness
leds: leds-nuc: allow changing the LED colors
leds: leds-nuc: add support for WMI API version 1.0
leds: leds-nuc: add basic support for NUC6 WMI
leds: leds-nuc: add brightness and color for NUC6 API
leds: leds-nuc: Add support to blink behavior for NUC8/10
leds: leds-nuc: get rid of an unused variable
leds: leds-nuc: implement blink control for NUC6
leds: leds-nuc: better detect NUC6/NUC7 devices
leds: leds-nuc: add support for HDD activity default
leds: leds-nuc: fix software blink behavior logic
leds: leds-nuc: add support for changing the ethernet type indicator
leds: leds-nuc: add support for changing the power limit scheme
Documentation/leds/index.rst | 1 + Documentation/leds/leds-nuc.rst | 447 +++++++ MAINTAINERS | 7 + drivers/leds/Kconfig | 8 + drivers/leds/Makefile | 1 + drivers/leds/leds-nuc.c | 2097 +++++++++++++++++++++++++++++++ 6 files changed, 2561 insertions(+) create mode 100644 Documentation/leds/leds-nuc.rst create mode 100644 drivers/leds/leds-nuc.c
Signature · 1 line
2.31.1
PATCH v2 1/17 docs: describe the API used to set NUC LEDs
Some NUC6 have LEDs that can be configurated dynamically from the operational system, via WMI.
Describe how the API for such devices should work.
--- Documentation/leds/index.rst | 1 + Documentation/leds/leds-nuc.rst | 447 ++++++++++++++++++++++++++++++++ 2 files changed, 448 insertions(+) create mode 100644 Documentation/leds/leds-nuc.rst
Documentation/leds/index.rst +1 -0
@@ -25,4 +25,5 @@ LEDs leds-lp5562 leds-lp55xx leds-mlxcpld + leds-nuc leds-sc27xx
Documentation/leds/leds-nuc.rst new file +447 -0
447 lines not on this page · raw message
Signature · 1 line
2.31.1
PATCH v2 2/17 leds: add support for NUC WMI LEDs
In reply to Mauro Carvalho ChehabSome Intel Next Unit of Computing (NUC) machines have software-configured LEDs that can be used to display a variety of events:
- Power State
- HDD Activity
- Ethernet
- WiFi
- Power Limit
They can even be controlled directly via software, without any hardware-specific indicator connected into them.
Some devices have mono-colored LEDs, but the more advanced ones have RGB leds that can show any color.
Different color and 4 blink states can be programmed for thee system states:
- powered on (S0);
- S3;
- Standby.
The NUC BIOSes allow to partially set them for S0, but doesn't provide any control for the other states, nor does allow changing the blinking logic.
They all use a WMI interface using GUID:
8C5DA44C-CDC3-46b3-8619-4E26D34390B7
But there are 3 different revisions of the spec, all using the same GUID, but two different APIs:
- the original one, for NUC6 and to NUCi7:
- https://www.intel.com/content/www/us/en/support/articles/000023426/intel-nuc/intel-nuc-kits.html
- a new one, starting with NUCi8, with two revisions:
- https://raw.githubusercontent.com/nomego/intel_nuc_led/master/specs/INTEL_WMI_LED_0.64.pdf
- https://www.intel.com/content/dam/support/us/en/documents/intel-nuc/WMI-Spec-Intel-NUC-NUC10ixFNx.pdf
There are some OOT drivers for them, but they use procfs and use a messy interface to setup it. Also, there are different drivers with the same name, each with support for each NUC family.
Let's start a new driver from scratch, using the x86 platform WMI core and the LED class.
This initial version is compatible with NUCi8 and above, and it was tested with a Hades Canyon NUC (NUC8i7HNK).
It provides just the more basic WMI support, allowing to change the LED hardware/firmware indicator for each LED in runtime.
--- MAINTAINERS | 7 + drivers/leds/Kconfig | 8 + drivers/leds/Makefile | 1 + drivers/leds/leds-nuc.c | 481 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 497 insertions(+) create mode 100644 drivers/leds/leds-nuc.c
MAINTAINERS +7 -0
@@ -13063,6 +13063,13 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/aia21/ntfs.git F: Documentation/filesystems/ntfs.rst F: fs/ntfs/ +NUC LED DRIVER +M: Mauro Carvalho Chehab <[email protected]> +L: [email protected] +S: Maintained +T: git git://git.kernel.org/pub/scm/linux/kernel/git/pavel/linux-leds.git +F: drivers/staging/nuc-led + NUBUS SUBSYSTEM M: Finn Thain <[email protected]> L: [email protected]
drivers/leds/Kconfig +8 -0
@@ -271,6 +271,14 @@ config LEDS_MT6323 This option enables support for on-chip LED drivers found on Mediatek MT6323 PMIC. +config LEDS_NUC_WMI + tristate "LED Support for Intel NUC" + depends on LEDS_CLASS + depends on ACPI_WMI + help + This option enables support for the WMI interface for LEDs + present on certain Intel NUC models. + config LEDS_S3C24XX tristate "LED Support for Samsung S3C24XX GPIO LEDs" depends on LEDS_CLASS
drivers/leds/Makefile +1 -0
@@ -71,6 +71,7 @@ obj-$(CONFIG_LEDS_MT6323) += leds-mt6323.o obj-$(CONFIG_LEDS_NET48XX) += leds-net48xx.o obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o obj-$(CONFIG_LEDS_NIC78BX) += leds-nic78bx.o +obj-$(CONFIG_LEDS_NUC_WMI) += leds-nuc.o obj-$(CONFIG_LEDS_NS2) += leds-ns2.o obj-$(CONFIG_LEDS_OT200) += leds-ot200.o obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.o
drivers/leds/leds-nuc.c new file +481 -0
481 lines not on this page · raw message
Signature · 1 line
2.31.1
PATCH v2 3/17 leds: leds-nuc: detect WMI API detection
In reply to Mauro Carvalho ChehabThere are currently 3 known API releases:
- https://www.intel.com/content/www/us/en/support/articles/000023426/intel-nuc/intel-nuc-kits.html
- https://raw.githubusercontent.com/nomego/intel_nuc_led/master/specs/INTEL_WMI_LED_0.64.pdf
- https://www.intel.com/content/dam/support/us/en/documents/intel-nuc/WMI-Spec-Intel-NUC-NUC10ixFNx.pdf
Add a logic to detect between them, preventing the driver to work with an unsupported version.
--- drivers/leds/leds-nuc.c | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-)
drivers/leds/leds-nuc.c +30 -3
@@ -26,6 +26,13 @@#define NUM_INPUT_ARGS 4#define NUM_OUTPUT_ARGS 3 +enum led_api_rev { +LED_API_UNKNOWN , +LED_API_NUC6 , +LED_API_REV_0_64 , +LED_API_REV_1_0 , +}; +enum led_cmds {LED_QUERY =0x03 ,LED_NEW_GET_STATUS =0x04 ,
@@ -33,6 +40,7 @@ enum led_cmds {LED_SET_VALUE =0x06 ,LED_NOTIFICATION =0x07 ,LED_SWITCH_TYPE =0x08 , + LED_VERSION_CONTROL = 0x09, }; enum led_query_subcmd {
@@ -195,7 +203,7 @@ static int nuc_wmi_query_leds(struct device *dev)struct nuc_wmi *priv =dev_get_drvdata (dev);u8 cmd, input[NUM_INPUT_ARGS ] = {0 };u8 output[NUM_OUTPUT_ARGS ]; -int i, id, ret; +int i, id, ret, ver =LED_API_UNKNOWN ;u8 leds;/*
@@ -209,12 +217,31 @@ static int nuc_wmi_query_leds(struct device *dev) cmd =LED_QUERY ; input[0 ] =LED_QUERY_LIST_ALL ; ret =nuc_nmi_cmd (dev, cmd, input, output); -if (ret) { +if (ret == -ENOENT ) { + ver =LED_API_NUC6 ; + }else if (ret) {dev_warn (dev,"error %d while listing all LEDs\n" , ret);return ret; + }else { + leds = output[0 ]; } - leds = output[0 ]; +if (ver !=LED_API_NUC6 ) { + ret =nuc_nmi_cmd (dev,LED_VERSION_CONTROL , input, output); +if (ret) +return ret; + + ver = output[0 ] | output[1 ] <<16 ; +if (!ver) + ver =LED_API_REV_0_64 ; +else if (ver ==0x0126 ) + ver =LED_API_REV_1_0 ; + } + +/* Currently, only API Revision 0.64 is supported */ +if (ver !=LED_API_REV_0_64 ) +return -ENODEV ; +if (!leds) {dev_warn (dev,"No LEDs found\n" );return -ENODEV ;
Signature · 1 line
2.31.1
PATCH v2 4/17 leds: leds-nuc: add support for changing S0 brightness
In reply to Mauro Carvalho ChehabNow that the core logic is in place, let's add support to adjust the S0 brightness level.
--- drivers/leds/leds-nuc.c | 79 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+)
drivers/leds/leds-nuc.c +79 -0
@@ -395,7 +395,85 @@ static ssize_t store_indicator(struct device *dev,return -EINVAL ; } +/* Get S0 brightness */ +static ssize_t show_s0_brightness (struct device *dev, +struct device_attribute *attr, +char *buf) +{ +struct led_classdev *cdev =dev_get_drvdata (dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); +u8 cmd, input[NUM_INPUT_ARGS ] = {0 }; +u8 output[NUM_OUTPUT_ARGS ]; +int ret; + + cmd =LED_NEW_GET_STATUS ; + input[0 ] =LED_NEW_GET_CONTROL_ITEM ; + input[1 ] = led->id; + input[2 ] = led->indicator; + input[3 ] =0 ; + + ret =nuc_nmi_cmd (dev, cmd, input, output); +if (ret) +return ret; + +/* Multicolor uses a scale from 0 to 100 */ +if (led->color_type & (LED_BLUE_AMBER |LED_BLUE_WHITE |LED_RGB )) +return scnprintf (buf,PAGE_SIZE ,"%d%%\n" , output[0 ]); + +/* single color uses 0, 50% and 100% */ +return scnprintf (buf,PAGE_SIZE ,"%d%%\n" , output[0 ] *50 ); +} + +/* Change S0 brightness */ +static ssize_t store_s0_brightness (struct device *dev, +struct device_attribute *attr, +const char *buf,size_t len) +{ +struct led_classdev *cdev =dev_get_drvdata (dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); +u8 cmd, input[NUM_INPUT_ARGS ] = {0 }; +int ret; +u8 val; + +if (led->indicator ==LED_IND_DISABLE ) { +dev_dbg (dev,"Led %s is disabled. ignoring it.\n" , cdev->name); +return -EACCES ; + } + +if (kstrtou8 (buf,0 , &val) || val >100 ) +return -EINVAL ; + +/* +* For single-color LEDs, the value should be between 0 to 2, but, +* in order to have a consistent API, let's always handle it as if +* it is a percentage, for both multicolor and single color LEDs. +* So: +* value == 0 will disable the LED +* value up to 74% will set it the brightness to 50% +* value equal or above 75% will use the maximum brightness. +*/ +if (!(led->color_type & (LED_BLUE_AMBER |LED_BLUE_WHITE |LED_RGB ))) { +if (val >0 && val <75 ) + val =1 ; +if (val >=75 ) + val =2 ; + } + + cmd =LED_SET_VALUE ; + input[0 ] = led->id; + input[1 ] = led->indicator; + input[2 ] =0 ;/* FIXME: replace by an enum */ + input[3 ] = val; + + ret =nuc_nmi_cmd (dev, cmd, input,NULL ); +if (ret) +return ret; + +return len; +} +static LED_ATTR_RW (indicator); +static LED_ATTR_RW (s0_brightness);/* * Attributes for multicolor LEDs
@@ -403,6 +481,7 @@ static LED_ATTR_RW(indicator);static struct attribute *nuc_wmi_multicolor_led_attr[] = { &dev_attr_indicator.attr, + &dev_attr_s0_brightness.attr,NULL , };
Signature · 1 line
2.31.1
PATCH v2 5/17 leds: leds-nuc: add all types of brightness
In reply to Mauro Carvalho ChehabImprove the logic in order to support not only S0 brightness, but also the brightness for other indicators and for all power states.
--- drivers/leds/leds-nuc.c | 369 +++++++++++++++++++++++++++------------- 1 file changed, 249 insertions(+), 120 deletions(-)
drivers/leds/leds-nuc.c +249 -120
@@ -55,21 +55,89 @@ enum led_new_get_subcmd { LED_NEW_GET_CONTROL_ITEM = 0x01, }; +enum led_function { +LED_FUNC_BRIGHTNESS , +LED_FUNC_COLOR1 , +LED_FUNC_COLOR_GREEN , +LED_FUNC_COLOR_BLUE , + +LED_FUNC_BLINK_BEHAVIOR , +LED_FUNC_BLINK_FREQ , + +LED_FUNC_HDD_BEHAVIOR , +LED_FUNC_ETH_TYPE , +LED_FUNC_POWER_LIMIT_SCHEME , + +MAX_LED_FUNC +}; + +enum led_indicators { +LED_IND_POWER_STATE , +LED_IND_HDD_ACTIVITY , +LED_IND_ETHERNET , +LED_IND_WIFI , +LED_IND_SOFTWARE , +LED_IND_POWER_LIMIT , +LED_IND_DISABLE , + +MAX_IND =LED_IND_DISABLE +}; + +/* +* control items ID for each of the valid indicators on spec Rev 0.64. +*/ +static const u8 led_func_rev_0_64[MAX_IND ][MAX_LED_FUNC ] = { + [LED_IND_POWER_STATE ] = {/* Offsets for each power state */ + [LED_FUNC_BRIGHTNESS ] =0x00 , + [LED_FUNC_BLINK_BEHAVIOR ] =0x01 , + [LED_FUNC_BLINK_FREQ ] =0x02 , + [LED_FUNC_COLOR1 ] =0x03 , + [LED_FUNC_COLOR_GREEN ] =0x04 , + [LED_FUNC_COLOR_BLUE ] =0x05 + }, + [LED_IND_HDD_ACTIVITY ] = { + [LED_FUNC_BRIGHTNESS ] =0x00 , + [LED_FUNC_COLOR1 ] =0x01 , + [LED_FUNC_COLOR_GREEN ] =0x02 , + [LED_FUNC_COLOR_BLUE ] =0x03 , + [LED_FUNC_HDD_BEHAVIOR ] =0x04 + }, + [LED_IND_ETHERNET ] = { + [LED_FUNC_ETH_TYPE ] =0x00 , + [LED_FUNC_BRIGHTNESS ] =0x01 , + [LED_FUNC_COLOR1 ] =0x02 , + [LED_FUNC_COLOR_GREEN ] =0x03 , + [LED_FUNC_COLOR_BLUE ] =0x04 + }, + [LED_IND_WIFI ] = { + [LED_FUNC_BRIGHTNESS ] =0x00 , + [LED_FUNC_COLOR1 ] =0x01 , + [LED_FUNC_COLOR_GREEN ] =0x02 , + [LED_FUNC_COLOR_BLUE ] =0x03 + }, + [LED_IND_SOFTWARE ] = { + [LED_FUNC_BRIGHTNESS ] =0x00 , + [LED_FUNC_BLINK_BEHAVIOR ] =0x01 , + [LED_FUNC_BLINK_FREQ ] =0x02 , + [LED_FUNC_COLOR1 ] =0x03 , + [LED_FUNC_COLOR_GREEN ] =0x04 , + [LED_FUNC_COLOR_BLUE ] =0x05 + }, + [LED_IND_POWER_LIMIT ] = { + [LED_FUNC_POWER_LIMIT_SCHEME ] =0x00 , + [LED_FUNC_BRIGHTNESS ] =0x01 , + [LED_FUNC_COLOR1 ] =0x02 , + [LED_FUNC_COLOR_GREEN ] =0x03 , + [LED_FUNC_COLOR_BLUE ] =0x04 + }, +}; +/* LED color indicator */ #define LED_BLUE_AMBER BIT(0)#define LED_BLUE_WHITE BIT(1)#define LED_RGB BIT(2)#define LED_SINGLE_COLOR BIT(3) -/* LED indicator options */ -#define LED_IND_POWER_STATE BIT(0) -#define LED_IND_HDD_ACTIVITY BIT(1) -#define LED_IND_ETHERNET BIT(2) -#define LED_IND_WIFI BIT(3) -#define LED_IND_SOFTWARE BIT(4) -#define LED_IND_POWER_LIMIT BIT(5) -#define LED_IND_DISABLE BIT(6) -static const char *const led_names[] = {"nuc::power" ,"nuc::hdd" ,
@@ -87,7 +155,6 @@ struct nuc_nmi_led {u8 indicator;u32 color_type;u32 avail_indicators; -u32 control_items; };struct nuc_wmi {
@@ -201,9 +268,9 @@ static int nuc_nmi_cmd(struct device *dev,static int nuc_wmi_query_leds (struct device *dev) {struct nuc_wmi *priv =dev_get_drvdata (dev); -u8 cmd, input[NUM_INPUT_ARGS ] = {0 }; +u8 input[NUM_INPUT_ARGS ] = {0 };u8 output[NUM_OUTPUT_ARGS ]; -int i, id, ret, ver =LED_API_UNKNOWN ; +int id, ret, ver =LED_API_UNKNOWN ;u8 leds;/*
@@ -214,9 +281,8 @@ static int nuc_wmi_query_leds(struct device *dev)* FIXME: Should add a fallback code for it to work with older NUCs, * as LED_QUERY returns an error on older devices like Skull Canyon. */ - cmd =LED_QUERY ; input[0 ] =LED_QUERY_LIST_ALL ; - ret =nuc_nmi_cmd (dev, cmd, input, output); + ret =nuc_nmi_cmd (dev,LED_QUERY , input, output);if (ret == -ENOENT ) { ver =LED_API_NUC6 ; }else if (ret) {
@@ -255,12 +321,11 @@ static int nuc_wmi_query_leds(struct device *dev) led->id = id; - cmd =LED_QUERY ; input[0 ] =LED_QUERY_COLOR_TYPE ; input[1 ] = id; - ret =nuc_nmi_cmd (dev, cmd, input, output); + ret =nuc_nmi_cmd (dev,LED_QUERY , input, output);if (ret) { -dev_warn (dev,"error %d on led %i\n" , ret, i); +dev_warn (dev,"error %d on led %i\n" , ret, id);return ret; }
@@ -268,23 +333,11 @@ static int nuc_wmi_query_leds(struct device *dev) output[1 ] <<8 | output[2 ] <<16 ; - cmd =LED_NEW_GET_STATUS ; - input[0 ] =LED_NEW_GET_CURRENT_INDICATOR ; - input[1 ] = i; - ret =nuc_nmi_cmd (dev, cmd, input, output); -if (ret) { -dev_warn (dev,"error %d on led %i\n" , ret, i); -return ret; - } - - led->indicator = output[0 ]; - - cmd =LED_QUERY ; input[0 ] =LED_QUERY_INDICATOR_OPTIONS ; - input[1 ] = i; - ret =nuc_nmi_cmd (dev, cmd, input, output); + input[1 ] = id; + ret =nuc_nmi_cmd (dev,LED_QUERY , input, output);if (ret) { -dev_warn (dev,"error %d on led %i\n" , ret, i); +dev_warn (dev,"error %d on led %i\n" , ret, id);return ret; }
@@ -292,23 +345,18 @@ static int nuc_wmi_query_leds(struct device *dev) output[1 ] <<8 | output[2 ] <<16 ; - cmd =LED_QUERY ; - input[0 ] =LED_QUERY_CONTROL_ITEMS ; - input[1 ] = i; - input[2 ] = led->indicator; - ret =nuc_nmi_cmd (dev, cmd, input, output); + input[0 ] =LED_NEW_GET_CURRENT_INDICATOR ; + input[1 ] = id; + ret =nuc_nmi_cmd (dev,LED_NEW_GET_STATUS , input, output);if (ret) { -dev_warn (dev,"error %d on led %i\n" , ret, i); +dev_warn (dev,"error %d on led %i\n" , ret, id);return ret; } + led->indicator = output[0 ]; - led->control_items = output[0 ] | - output[1 ] <<8 | - output[2 ] <<16 ; - -dev_dbg (dev,"%s: id: %02x, color type: %06x, indicator: %06x, control items: %06x\n" , - led_names[led->id], led->id, - led->color_type, led->indicator, led->control_items); +dev_dbg (dev,"%s: id: %02x, color type: %06x, indicator: %02x (avail %06x)\n" , + led_names[led->id], led->id, led->color_type, + led->indicator, led->avail_indicators); priv->num_leds++; }
@@ -316,6 +364,82 @@ static int nuc_wmi_query_leds(struct device *dev)return 0 ; } +static bool nuc_wmi_test_control (struct device *dev, +struct nuc_nmi_led *led,u8 ctrl) +{ +int ret, avail_ctrls; +u8 output[NUM_OUTPUT_ARGS ]; +u8 input[NUM_INPUT_ARGS ] = { +LED_QUERY_CONTROL_ITEMS , + led->id, + led->indicator + }; + + ret =nuc_nmi_cmd (dev,LED_QUERY , input, output); +if (ret) +return false; + + avail_ctrls = output[0 ] | + output[1 ] <<8 | + output[2 ] <<16 ; + +return avail_ctrls &BIT (ctrl); +} + +static int nuc_wmi_get_brightness_offset (struct device *dev, +struct nuc_nmi_led *led,u8 offset) +{ +u8 input[NUM_INPUT_ARGS ]; +u8 output[NUM_OUTPUT_ARGS ]; +int ret, ctrl; + +if (led->indicator ==LED_IND_DISABLE ) +return -ENODEV ; + + ctrl = led_func_rev_0_64[led->indicator][LED_FUNC_BRIGHTNESS ] + offset; + +if (!nuc_wmi_test_control (dev, led, ctrl)) +return -ENODEV ; + + input[0 ] =LED_NEW_GET_CONTROL_ITEM ; + input[1 ] = led->id; + input[2 ] = led->indicator; + input[3 ] = ctrl; + + ret =nuc_nmi_cmd (dev,LED_NEW_GET_STATUS , input, output); +if (ret) +return ret; + +dev_dbg (dev,"%s: id: %02x, brightness: %02x\n" , + led_names[led->id], led->id, output[0 ]); + +return output[0 ]; +} + +static ssize_t nuc_wmi_set_brightness_offset (struct device *dev, +struct nuc_nmi_led *led, +u8 offset, +u8 val) +{ +u8 input[NUM_INPUT_ARGS ]; +int ctrl; + +if (led->indicator ==LED_IND_DISABLE ) +return -ENODEV ; + + ctrl = led_func_rev_0_64[led->indicator][LED_FUNC_BRIGHTNESS ] + offset; + +if (!nuc_wmi_test_control (dev, led, ctrl)) +return -ENODEV ; + + input[0 ] = led->id; + input[1 ] = led->indicator; + input[2 ] = ctrl; + input[3 ] = val; + +return nuc_nmi_cmd (dev,LED_SET_VALUE , input,NULL ); +} +/* * LED show/store routines */
@@ -323,6 +447,21 @@ static int nuc_wmi_query_leds(struct device *dev)#define LED_ATTR_RW (_name) \ DEVICE_ATTR(_name, 0644, show_##_name, store_##_name) +#define LED_ATTR_POWER_STATE_RW (_name, offset) \ + static ssize_t show_##_name(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ + { \ + return show_brightness_offset(dev, attr, offset, buf); \ + } \ + static ssize_t store_##_name(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t len) \ + { \ + return store_brightness_offset(dev, attr, offset, buf, len); \ + } \ + static DEVICE_ATTR(_name, 0644, show_##_name, store_##_name) +static const char *const led_indicators[] = {"Power State" ,"HDD Activity" ,
@@ -395,98 +534,93 @@ static ssize_t store_indicator(struct device *dev,return -EINVAL ; } -/* Get S0 brightness */ -static ssize_t show_s0_brightness (struct device *dev, -struct device_attribute *attr, -char *buf) +/* Get brightness */ +static ssize_t show_brightness_offset (struct device *dev, +struct device_attribute *attr, +u8 offset, +char *buf) {struct led_classdev *cdev =dev_get_drvdata (dev); struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); -u8 cmd, input[NUM_INPUT_ARGS ] = {0 }; -u8 output[NUM_OUTPUT_ARGS ];int ret; - cmd =LED_NEW_GET_STATUS ; - input[0 ] =LED_NEW_GET_CONTROL_ITEM ; - input[1 ] = led->id; - input[2 ] = led->indicator; - input[3 ] =0 ; +if (led->indicator !=LED_IND_POWER_STATE ) +return -ENODEV ; - ret =nuc_nmi_cmd (dev, cmd, input, output); -if (ret) + ret =nuc_wmi_get_brightness_offset (dev, led, offset); +if (ret <0 )return ret; -/* Multicolor uses a scale from 0 to 100 */ -if (led->color_type & (LED_BLUE_AMBER |LED_BLUE_WHITE |LED_RGB )) -return scnprintf (buf,PAGE_SIZE ,"%d%%\n" , output[0 ]); - -/* single color uses 0, 50% and 100% */ -return scnprintf (buf,PAGE_SIZE ,"%d%%\n" , output[0 ] *50 ); +return scnprintf (buf,PAGE_SIZE ,"%d\n" , ret); } -/* Change S0 brightness */ -static ssize_t store_s0_brightness (struct device *dev, -struct device_attribute *attr, -const char *buf,size_t len) +/* Change brightness */ +static ssize_t store_brightness_offset (struct device *dev, +struct device_attribute *attr, +u8 offset, +const char *buf,size_t len) {struct led_classdev *cdev =dev_get_drvdata (dev); struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); -u8 cmd, input[NUM_INPUT_ARGS ] = {0 };int ret;u8 val; -if (led->indicator ==LED_IND_DISABLE ) { -dev_dbg (dev,"Led %s is disabled. ignoring it.\n" , cdev->name); -return -EACCES ; - } +if (led->indicator !=LED_IND_POWER_STATE ) +return -ENODEV ;if (kstrtou8 (buf,0 , &val) || val >100 )return -EINVAL ; -/* -* For single-color LEDs, the value should be between 0 to 2, but, -* in order to have a consistent API, let's always handle it as if -* it is a percentage, for both multicolor and single color LEDs. -* So: -* value == 0 will disable the LED -* value up to 74% will set it the brightness to 50% -* value equal or above 75% will use the maximum brightness. -*/ -if (!(led->color_type & (LED_BLUE_AMBER |LED_BLUE_WHITE |LED_RGB ))) { -if (val >0 && val <75 ) - val =1 ; -if (val >=75 ) - val =2 ; - } - - cmd =LED_SET_VALUE ; - input[0 ] = led->id; - input[1 ] = led->indicator; - input[2 ] =0 ;/* FIXME: replace by an enum */ - input[3 ] = val; - - ret =nuc_nmi_cmd (dev, cmd, input,NULL ); + ret =nuc_wmi_set_brightness_offset (dev, led, offset, val);if (ret)return ret;return len; } +static enum led_brightness nuc_wmi_get_brightness (struct led_classdev *cdev) +{ + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); + +if (led->indicator ==LED_IND_POWER_STATE ) +return -ENODEV ; + +return nuc_wmi_get_brightness_offset (cdev->dev, led,0 ); +} + +static int nuc_wmi_set_brightness (struct led_classdev *cdev, +enum led_brightness brightness) +{ + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); + +if (led->indicator ==LED_IND_POWER_STATE ) +return -ENODEV ; + +return nuc_wmi_set_brightness_offset (cdev->dev, led,0 , brightness); +} +static LED_ATTR_RW (indicator); -static LED_ATTR_RW (s0_brightness); + +LED_ATTR_POWER_STATE_RW (s0_brightness,0x00 ); +LED_ATTR_POWER_STATE_RW (s3_brightness,0x06 ); +LED_ATTR_POWER_STATE_RW (s5_brightness,0x0c ); +LED_ATTR_POWER_STATE_RW (ready_mode_brightness,0x12 );/* -* Attributes for multicolor LEDs +* Attributes for LEDs */ -static struct attribute *nuc_wmi_multicolor_led_attr[] = { +static struct attribute *nuc_wmi_led_attr[] = { &dev_attr_indicator.attr, &dev_attr_s0_brightness.attr, + &dev_attr_s3_brightness.attr, + &dev_attr_s5_brightness.attr, + &dev_attr_ready_mode_brightness.attr,NULL , };static const struct attribute_group nuc_wmi_led_attribute_group = { - .attrs = nuc_wmi_multicolor_led_attr, + .attrs = nuc_wmi_led_attr, };static const struct attribute_group *nuc_wmi_led_attribute_groups[] = {
@@ -496,30 +630,25 @@ static const struct attribute_group *nuc_wmi_led_attribute_groups[] = {static int nuc_wmi_led_register (struct device *dev,struct nuc_nmi_led *led) { +int brightness =nuc_wmi_get_brightness_offset (dev, led,0 ); + led->cdev.name = led_names[led->id]; - led->dev = dev; led->cdev.groups = nuc_wmi_led_attribute_groups; + led->cdev.brightness_get = nuc_wmi_get_brightness; + led->cdev.brightness_set_blocking = nuc_wmi_set_brightness; -/* -* It can't let the classdev to manage the brightness due to several -* reasons: -* -* 1) classdev has some internal logic to manage the brightness, -* at set_brightness_delayed(), which starts disabling the LEDs; -* While this makes sense on most cases, here, it would appear -* that the NUC was powered off, which is not what happens; -* 2) classdev unconditionally tries to set brightness for all -* leds, including the ones that were software-disabled or -* disabled disabled via BIOS menu; -* 3) There are 3 types of brightness values for each LED, depending -* on the CPU power state: S0, S3 and S5. -* -* So, the best seems to export everything via sysfs attributes -* directly. This would require some further changes at the -* LED class, though, or we would need to create our own LED -* class, which seems wrong. -*/ +if (led->color_type &LED_SINGLE_COLOR ) + led->cdev.max_brightness =2 ; +else + led->cdev.max_brightness =100 ; + +/* Ensure that the current bright will be preserved */ +if (brightness >=0 ) + led->cdev.delayed_set_value = brightness; + +/* Suppress warnings for the LED(s) indicating the power state */ + led->cdev.flags =LED_HW_PLUGGABLE |LED_UNREGISTERING ;return devm_led_classdev_register (dev, &led->cdev); }
Signature · 1 line
2.31.1
PATCH v2 6/17 leds: leds-nuc: allow changing the LED colors
In reply to Mauro Carvalho ChehabAdd routines to allow seeing and changing the LED colors.
--- drivers/leds/leds-nuc.c | 244 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 228 insertions(+), 16 deletions(-)
drivers/leds/leds-nuc.c +228 -16
@@ -58,8 +58,6 @@ enum led_new_get_subcmd {enum led_function {LED_FUNC_BRIGHTNESS ,LED_FUNC_COLOR1 , -LED_FUNC_COLOR_GREEN , -LED_FUNC_COLOR_BLUE ,LED_FUNC_BLINK_BEHAVIOR ,LED_FUNC_BLINK_FREQ ,
@@ -92,43 +90,31 @@ static const u8 led_func_rev_0_64[MAX_IND][MAX_LED_FUNC] = { [LED_FUNC_BLINK_BEHAVIOR] = 0x01, [LED_FUNC_BLINK_FREQ] = 0x02, [LED_FUNC_COLOR1] = 0x03, - [LED_FUNC_COLOR_GREEN] = 0x04, - [LED_FUNC_COLOR_BLUE ] =0x05 }, [LED_IND_HDD_ACTIVITY] = { [LED_FUNC_BRIGHTNESS] = 0x00, [LED_FUNC_COLOR1] = 0x01, - [LED_FUNC_COLOR_GREEN] = 0x02, - [LED_FUNC_COLOR_BLUE] = 0x03, [LED_FUNC_HDD_BEHAVIOR ] =0x04 }, [LED_IND_ETHERNET] = { [LED_FUNC_ETH_TYPE] = 0x00, [LED_FUNC_BRIGHTNESS] = 0x01, [LED_FUNC_COLOR1] = 0x02, - [LED_FUNC_COLOR_GREEN] = 0x03, - [LED_FUNC_COLOR_BLUE ] =0x04 }, [LED_IND_WIFI] = { [LED_FUNC_BRIGHTNESS] = 0x00, [LED_FUNC_COLOR1] = 0x01, - [LED_FUNC_COLOR_GREEN] = 0x02, - [LED_FUNC_COLOR_BLUE ] =0x03 }, [LED_IND_SOFTWARE] = { [LED_FUNC_BRIGHTNESS] = 0x00, [LED_FUNC_BLINK_BEHAVIOR] = 0x01, [LED_FUNC_BLINK_FREQ] = 0x02, [LED_FUNC_COLOR1] = 0x03, - [LED_FUNC_COLOR_GREEN] = 0x04, - [LED_FUNC_COLOR_BLUE ] =0x05 }, [LED_IND_POWER_LIMIT] = { [LED_FUNC_POWER_LIMIT_SCHEME] = 0x00, [LED_FUNC_BRIGHTNESS] = 0x01, [LED_FUNC_COLOR1] = 0x02, - [LED_FUNC_COLOR_GREEN] = 0x03, - [LED_FUNC_COLOR_BLUE ] =0x04 }, };
@@ -462,6 +448,8 @@ static ssize_t nuc_wmi_set_brightness_offset(struct device *dev, } \ static DEVICE_ATTR(_name, 0644, show_##_name, store_##_name) +/* Show/change the LED indicator */ + static const char * const led_indicators[] = {"Power State" ,"HDD Activity" ,
@@ -534,7 +522,220 @@ static ssize_t store_indicator(struct device *dev,return -EINVAL ; } -/* Get brightness */ +/* Show/change the LED color */ + +enum led_colors { +LED_COLOR_BLUE , +LED_COLOR_AMBER , +LED_COLOR_WHITE +}; + +struct led_color_name { +const char *name; +u8 r, g, b; +}; + +static const struct led_color_name led_colors[] = { +/* The first colors should match the dual-LED colorset */ + [LED_COLOR_BLUE ] = {"blue" ,0 ,0 ,0xff }, + [LED_COLOR_AMBER ] = {"amber" ,0xff ,0xbf ,0 }, + [LED_COLOR_WHITE ] = {"white" ,0xff ,0xff ,0xff }, + +/* Let's add a couple of common color names as well */ + {"red" ,0xff ,0 ,0 }, + {"green" ,0 ,0xff ,0 }, + {"yellow" ,0xff ,0xff ,0 }, + {"cyan" ,0 ,0xff ,0xff }, + {"magenta" ,0xff ,0 ,0xff }, +}; + +static ssize_t show_color (struct device *dev, +struct device_attribute *attr,char *buf) +{ +struct led_classdev *cdev =dev_get_drvdata (dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); +u8 input[NUM_INPUT_ARGS ]; +u8 output[NUM_OUTPUT_ARGS ]; +int ret, ctrl; +int size =PAGE_SIZE ; +char *p = buf; +int color, r, g, b; + +if (led->indicator ==LED_IND_DISABLE ) +return -ENODEV ; + + ctrl = led_func_rev_0_64[led->indicator][LED_FUNC_COLOR1 ]; + +if (!nuc_wmi_test_control (dev, led, ctrl)) +return -ENODEV ; + + input[0 ] =LED_NEW_GET_CONTROL_ITEM ; + input[1 ] = led->id; + input[2 ] = led->indicator; + input[3 ] = ctrl; + + ret =nuc_nmi_cmd (dev,LED_NEW_GET_STATUS , input, output); +if (ret) +return ret; + +if (led->color_type &LED_RGB ) { + r = output[0 ]; + + input[3 ]++; + ret =nuc_nmi_cmd (dev,LED_NEW_GET_STATUS , input, output); +if (ret) +return ret; + + g = output[0 ]; + + input[3 ]++; + ret =nuc_nmi_cmd (dev,LED_NEW_GET_STATUS , input, output); +if (ret) +return ret; + + b = output[0 ]; + +for (color =0 ; color <ARRAY_SIZE (led_colors); color++) +if (led_colors[color].r == r && + led_colors[color].g == g && + led_colors[color].b == b) +return scnprintf (p, size,"%s\n" , + led_colors[color].name); + +return scnprintf (p, size,"%d,%d,%d\n" , r, g, b); + } + +if (!output[0 ]) +return scnprintf (p, size,"%s\n" , + led_colors[LED_COLOR_BLUE ].name); + +if (led->color_type &LED_BLUE_AMBER ) +return scnprintf (p, size,"%s\n" , + led_colors[LED_COLOR_AMBER ].name); + +return scnprintf (p, size,"%s\n" , led_colors[LED_COLOR_WHITE ].name); +} + +static ssize_t store_color (struct device *dev, +struct device_attribute *attr, +const char *buf,size_t len) +{ +struct led_classdev *cdev =dev_get_drvdata (dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); +u8 input[NUM_INPUT_ARGS ] = {0 }; +int ret, ctrl, color; +const char *tmp; +u8 r, g, b, val; + +if (led->indicator ==LED_IND_DISABLE ) +return -ENODEV ; + + tmp =strsep ((char **)&buf,",\n" ); + +for (color =0 ; color <ARRAY_SIZE (led_colors); color++) +if (!strcasecmp (tmp, led_colors[color].name)) +break ; + +if (color <ARRAY_SIZE (led_colors)) { + r = led_colors[color].r; + g = led_colors[color].g; + b = led_colors[color].b; + }else { +if (kstrtou8 (tmp,0 , &r) || r >255 ) +return -EINVAL ; + + tmp =strsep ((char **)&buf,",\n" ); +if (kstrtou8 (tmp,0 , &g) || g >255 ) +return -EINVAL ; + + tmp =strsep ((char **)&buf," \n" ); +if (kstrtou8 (tmp,0 , &b) || b >255 ) +return -EINVAL ; + +if (led->color_type &LED_SINGLE_COLOR ) { +for (color =0 ; color <=LED_COLOR_WHITE ; color++) +if (led_colors[color].r == r && + led_colors[color].g == g && + led_colors[color].b == b) +break ; + } + } + + ctrl = led_func_rev_0_64[led->indicator][LED_FUNC_COLOR1 ]; + +/* Dual color LEDs */ +if (!(led->color_type &LED_RGB )) { +if (color ==LED_COLOR_BLUE ) + val =0 ; +else { +if (led->color_type &LED_BLUE_AMBER && + color !=LED_COLOR_AMBER ) +return -EINVAL ; +else if (color !=LED_COLOR_WHITE ) +return -EINVAL ; + val =1 ; + } + + input[0 ] = led->id; + input[1 ] = led->indicator; + input[2 ] = ctrl; + input[3 ] = val; + + ret =nuc_nmi_cmd (dev,LED_SET_VALUE , input,NULL ); +if (ret) +return ret; + +return len; + } + +/* RGB LEDs */ + input[0 ] = led->id; + input[1 ] = led->indicator; + input[2 ] = ctrl; + input[3 ] = r; + + ret =nuc_nmi_cmd (dev,LED_SET_VALUE , input,NULL ); +if (ret) +return ret; + + input[0 ] = led->id; + input[1 ] = led->indicator; + input[2 ]++; + input[3 ] = g; + + ret =nuc_nmi_cmd (dev,LED_SET_VALUE , input,NULL ); +if (ret) +return ret; + + input[0 ] = led->id; + input[1 ] = led->indicator; + input[2 ]++; + input[3 ] = b; + + ret =nuc_nmi_cmd (dev,LED_SET_VALUE , input,NULL ); +if (ret) +return ret; + +return len; + +return -EINVAL ; +} + +static umode_t nuc_wmi_led_color_is_visible (struct kobject *kobj, +struct attribute *attr,int idx) +{ +struct device *dev =kobj_to_dev (kobj); +struct led_classdev *cdev =dev_get_drvdata (dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); +umode_t mode = attr->mode; + +if (led->color_type &LED_SINGLE_COLOR ) +return 0 ; + +return mode; +} + +/* Show/store brightness */ static ssize_t show_brightness_offset(struct device *dev, struct device_attribute *attr, u8 offset,
@@ -554,7 +755,6 @@ static ssize_t show_brightness_offset(struct device *dev,return scnprintf (buf,PAGE_SIZE ,"%d\n" , ret); } -/* Change brightness */ static ssize_t store_brightness_offset(struct device *dev, struct device_attribute *attr, u8 offset,
@@ -600,6 +800,7 @@ static int nuc_wmi_set_brightness(struct led_classdev *cdev, }static LED_ATTR_RW (indicator); +static LED_ATTR_RW (color);LED_ATTR_POWER_STATE_RW (s0_brightness,0x00 );LED_ATTR_POWER_STATE_RW (s3_brightness,0x06 );
@@ -623,8 +824,19 @@ static const struct attribute_group nuc_wmi_led_attribute_group = { .attrs = nuc_wmi_led_attr, }; +static struct attribute *nuc_wmi_led_color_attr[] = { + &dev_attr_color.attr, +NULL , +}; + +static const struct attribute_group nuc_wmi_led_color_attribute_group = { + .is_visible = nuc_wmi_led_color_is_visible, + .attrs = nuc_wmi_led_color_attr, +}; +static const struct attribute_group *nuc_wmi_led_attribute_groups[] = { &nuc_wmi_led_attribute_group, + &nuc_wmi_led_color_attribute_group,NULL };
Signature · 1 line
2.31.1
Re: [PATCH v2 06/17] leds: leds-nuc: allow changing the LED colors
We have multicolor LED framerwork in Linux. This should be implemented via that framework. Please do not implement your own way for RGB leds.
Marek
Re: [PATCH v2 06/17] leds: leds-nuc: allow changing the LED colors
Hi Marek,
Em Wed, 19 May 2021 09:57:13 +0200
Marek Behún <[email protected]> escreveu:
Quoted 4 lines
We have multicolor LED framerwork in Linux. This should be implemented via that framework. Please do not implement your own way for RGB leds.
Marek
I saw the multicolor LED framework, but IMO it won't fit here.
See, Linux doesn't have direct access to the LED. The access is provided via ACPI WMI.
The way BIOS reports the type of the led is via a bitmap flag. So, the same LED can be represented with either single-color or multi-color one. See:
https://www.intel.com/content/dam/support/us/en/documents/intel-nuc/WMI-Spec-Intel-NUC-NUC10ixFNx.pdf
Table 2.2 LED Color Type
Bit Number Type
0 Dual-color Blue / Amber
1 Dual-color Blue / White
2 RGB-color
3 Single-color LED
Also as each NUC can support up to 7, and they may have a mix of single color, dual color and RGB LEDs, if we would use the multicolor class for the colored ones, that would mean that the code will need to be duplicated, as, depending on what the BIOS reports, the LED would need to be exposed either as via led-multicolor-class or as leds-class.
Worse than that, there's even a WMI command that would allow to "switch LED type" (see page 8). On other words, the BIOS can expose a "virtual" single-color LED, but actually implemented in hardware using a RGB one, and this can be changed in real time.
(note: I didn't implement support yet for this, as the hardware I have doesn't seem to support the "switch LED type" command).
Thanks,
Mauro
Re: [PATCH v2 06/17] leds: leds-nuc: allow changing the LED colors
Hi!
Quoted 9 lines
Quoted 4 lines
We have multicolor LED framerwork in Linux. This should be implemented via that framework. Please do not implement your own way for RGB leds.
Marek
I saw the multicolor LED framework, but IMO it won't fit here.
See, Linux doesn't have direct access to the LED. The access is provided via ACPI WMI.
So?
Quoted 12 lines
The way BIOS reports the type of the led is via a bitmap flag. So, the same LED can be represented with either single-color or multi-color one. See:
https://www.intel.com/content/dam/support/us/en/documents/intel-nuc/WMI-Spec-Intel-NUC-NUC10ixFNx.pdfTable 2.2 LED Color Type
Bit Number Type
0 Dual-color Blue / Amber
1 Dual-color Blue / White
2 RGB-color
3 Single-color LED
Quoted 6 lines
Also as each NUC can support up to 7, and they may have a mix of single color, dual color and RGB LEDs, if we would use the multicolor class for the colored ones, that would mean that the code will need to be duplicated, as, depending on what the BIOS reports, the LED would need to be exposed either as via led-multicolor-class or as leds-class.
So?
Quoted 4 lines
Worse than that, there's even a WMI command that would allow to "switch LED type" (see page 8). On other words, the BIOS can expose a "virtual" single-color LED, but actually implemented in hardware using a RGB one, and this can be changed in real time.
So you simply always use it as RGB one?
Best regards,
Pavel
Signature · 1 line
http://www.livejournal.com/~pavelmachek
Re: [PATCH v2 06/17] leds: leds-nuc: allow changing the LED colors
Em Wed, 19 May 2021 13:07:25 +0200
Pavel Machek <[email protected]> escreveu:
Quoted 33 lines
Hi!
Quoted 9 lines
Quoted 4 lines
We have multicolor LED framerwork in Linux. This should be implemented via that framework. Please do not implement your own way for RGB leds.
Marek
I saw the multicolor LED framework, but IMO it won't fit here.
See, Linux doesn't have direct access to the LED. The access is provided via ACPI WMI.
So?
Quoted 12 lines
The way BIOS reports the type of the led is via a bitmap flag. So, the same LED can be represented with either single-color or multi-color one. See:
https://www.intel.com/content/dam/support/us/en/documents/intel-nuc/WMI-Spec-Intel-NUC-NUC10ixFNx.pdfTable 2.2 LED Color Type
Bit Number Type
0 Dual-color Blue / Amber
1 Dual-color Blue / White
2 RGB-color
3 Single-color LEDQuoted 6 lines
Also as each NUC can support up to 7, and they may have a mix of single color, dual color and RGB LEDs, if we would use the multicolor class for the colored ones, that would mean that the code will need to be duplicated, as, depending on what the BIOS reports, the LED would need to be exposed either as via led-multicolor-class or as leds-class.
Quoted 8 lines
So?
Quoted 4 lines
Worse than that, there's even a WMI command that would allow to "switch LED type" (see page 8). On other words, the BIOS can expose a "virtual" single-color LED, but actually implemented in hardware using a RGB one, and this can be changed in real time.
So you simply always use it as RGB one?
Hmm... are you meaning that I should only use the multicolor led class, even when the BIOS is reporting the LEDs as single color?
I can surely do that.
Quoted 3 lines
Best regards,
Pavel
Thanks,
Mauro
Re: [PATCH v2 06/17] leds: leds-nuc: allow changing the LED colors
Quoted 43 lines · On Wed, 19 May 2021 14:00:40 +0200 Mauro Carvalho Chehab…
Em Wed, 19 May 2021 13:07:25 +0200
Pavel Machek <[email protected]> escreveu:Quoted 40 lines
Hi!
Quoted 9 lines
Quoted 4 lines
We have multicolor LED framerwork in Linux. This should be implemented via that framework. Please do not implement your own way for RGB leds.
Marek
I saw the multicolor LED framework, but IMO it won't fit here.
See, Linux doesn't have direct access to the LED. The access is provided via ACPI WMI.
So?
Quoted 12 lines
The way BIOS reports the type of the led is via a bitmap flag. So, the same LED can be represented with either single-color or multi-color one. See:
https://www.intel.com/content/dam/support/us/en/documents/intel-nuc/WMI-Spec-Intel-NUC-NUC10ixFNx.pdfTable 2.2 LED Color Type
Bit Number Type
0 Dual-color Blue / Amber
1 Dual-color Blue / White
2 RGB-color
3 Single-color LEDQuoted 6 lines
Also as each NUC can support up to 7, and they may have a mix of single color, dual color and RGB LEDs, if we would use the multicolor class for the colored ones, that would mean that the code will need to be duplicated, as, depending on what the BIOS reports, the LED would need to be exposed either as via led-multicolor-class or as leds-class.
So?
Quoted 4 lines
Worse than that, there's even a WMI command that would allow to "switch LED type" (see page 8). On other words, the BIOS can expose a "virtual" single-color LED, but actually implemented in hardware using a RGB one, and this can be changed in real time.
Quoted 6 lines
Quoted 1 line
So you simply always use it as RGB one?
Hmm... are you meaning that I should only use the multicolor led class, even when the BIOS is reporting the LEDs as single color?
I can surely do that.
No. If the BIOS reports that the LED is single color, register a classic LED. If the BIOS reports a RGB LED, register a multi-color LED...
Marek
Re: [PATCH v2 06/17] leds: leds-nuc: allow changing the LED colors
Em Wed, 19 May 2021 14:04:51 +0200
Marek Behún <[email protected]> escreveu:
Quoted 48 lines
Quoted 45 lines · On Wed, 19 May 2021 14:00:40 +0200 Mauro Carvalho Chehab…
Em Wed, 19 May 2021 13:07:25 +0200
Pavel Machek <[email protected]> escreveu:Quoted 41 lines
Hi!
Quoted 9 lines
Quoted 4 lines
We have multicolor LED framerwork in Linux. This should be implemented via that framework. Please do not implement your own way for RGB leds.
Marek
I saw the multicolor LED framework, but IMO it won't fit here.
See, Linux doesn't have direct access to the LED. The access is provided via ACPI WMI.
So?
Quoted 12 lines
The way BIOS reports the type of the led is via a bitmap flag. So, the same LED can be represented with either single-color or multi-color one. See:
https://www.intel.com/content/dam/support/us/en/documents/intel-nuc/WMI-Spec-Intel-NUC-NUC10ixFNx.pdfTable 2.2 LED Color Type
Bit Number Type
0 Dual-color Blue / Amber
1 Dual-color Blue / White
2 RGB-color
3 Single-color LEDQuoted 6 lines
Also as each NUC can support up to 7, and they may have a mix of single color, dual color and RGB LEDs, if we would use the multicolor class for the colored ones, that would mean that the code will need to be duplicated, as, depending on what the BIOS reports, the LED would need to be exposed either as via led-multicolor-class or as leds-class.
So?
Quoted 4 lines
Worse than that, there's even a WMI command that would allow to "switch LED type" (see page 8). On other words, the BIOS can expose a "virtual" single-color LED, but actually implemented in hardware using a RGB one, and this can be changed in real time.
So you simply always use it as RGB one?
Quoted 8 lines
Quoted 4 lines
Hmm... are you meaning that I should only use the multicolor led class, even when the BIOS is reporting the LEDs as single color?
I can surely do that.
No. If the BIOS reports that the LED is single color, register a classic LED. If the BIOS reports a RGB LED, register a multi-color LED...
Ok. I'll try to change the code to work with both APIs. Let's hope it won't result on too much code duplication.
-
I have one doubt about how to use this API. It is meant just for the RGB LEDs, right?
NUC also have dual-color and multi-colored leds that don't allow specifying the intensity of each component. All it lets is to change the color (being, for instance, either blue or white).
For those, the API should basically show the names of the supported colors for each LED, letting the user change it to some other color.
How should I map such leds? via led-class or via
led-class-multicolor?
Thanks,
Mauro
PATCH v2 7/17 leds: leds-nuc: add support for WMI API version 1.0
In reply to Mauro Carvalho ChehabThe control indicators for WMI version 1.0 (used on NUCi10 and above) are on different locations.
The main difference is on single color LEDs.
Also, the Power State brightness names are defined on a different way, and there are 3 groups instead of 4.
As the driver was written with some tables to map the control option values, it is easy to extend it to support the new definitions: all we need to do is to add the V1.0 tables and ensure that the right table will be used.
--- drivers/leds/leds-nuc.c | 119 +++++++++++++++++++++++++++++++++------- 1 file changed, 99 insertions(+), 20 deletions(-)
drivers/leds/leds-nuc.c +99 -20
@@ -62,6 +62,7 @@ enum led_function {LED_FUNC_BLINK_BEHAVIOR ,LED_FUNC_BLINK_FREQ , +LED_FUNC_POWER_STATE_NUM_CTRLS ,LED_FUNC_HDD_BEHAVIOR ,LED_FUNC_ETH_TYPE , LED_FUNC_POWER_LIMIT_SCHEME,
@@ -84,8 +85,11 @@ enum led_indicators {/* * control items ID for each of the valid indicators on spec Rev 0.64. */ -static const u8 led_func_rev_0_64[MAX_IND ][MAX_LED_FUNC ] = { - [LED_IND_POWER_STATE ] = {/* Offsets for each power state */ +static const u8 led_func_multicolor[MAX_IND ][MAX_LED_FUNC ] = { + [LED_IND_POWER_STATE ] = { + [LED_FUNC_POWER_STATE_NUM_CTRLS ] =0x06 , + +/* Offsets for each power state */ [LED_FUNC_BRIGHTNESS ] =0x00 , [LED_FUNC_BLINK_BEHAVIOR ] =0x01 , [LED_FUNC_BLINK_FREQ ] =0x02 ,
@@ -118,6 +122,24 @@ static const u8 led_func_rev_0_64[MAX_IND][MAX_LED_FUNC] = { }, }; +static const u8 led_func_rev_1_0_singlecolor[MAX_IND ][MAX_LED_FUNC ] = { + [LED_IND_POWER_STATE ] = { + [LED_FUNC_POWER_STATE_NUM_CTRLS ] =0x02 , + +/* Offsets for each power state */ + [LED_FUNC_BRIGHTNESS ] =0x00 , + [LED_FUNC_BLINK_BEHAVIOR ] =0x01 , + }, + [LED_IND_HDD_ACTIVITY ] = { + [LED_FUNC_BRIGHTNESS ] =0x00 , + [LED_FUNC_HDD_BEHAVIOR ] =0x01 + }, + [LED_IND_SOFTWARE ] = { + [LED_FUNC_BRIGHTNESS ] =0x00 , + [LED_FUNC_BLINK_BEHAVIOR ] =0x01 , + }, +}; +/* LED color indicator */ #define LED_BLUE_AMBER BIT(0)#define LED_BLUE_WHITE BIT(1)
@@ -141,6 +163,9 @@ struct nuc_nmi_led {u8 indicator;u32 color_type;u32 avail_indicators; +enum led_api_rev api_rev; + +const u8 (*reg_table)[MAX_LED_FUNC ]; };struct nuc_wmi {
@@ -251,7 +276,7 @@ static int nuc_nmi_cmd(struct device *dev,return ret; } -static int nuc_wmi_query_leds (struct device *dev) +static int nuc_wmi_query_leds (struct device *dev,enum led_api_rev *api_rev) {struct nuc_wmi *priv =dev_get_drvdata (dev);u8 input[NUM_INPUT_ARGS ] = {0 };
@@ -291,9 +316,11 @@ static int nuc_wmi_query_leds(struct device *dev) }/* Currently, only API Revision 0.64 is supported */ -if (ver !=LED_API_REV_0_64 ) +if (ver !=LED_API_REV_0_64 && ver !=LED_API_REV_1_0 )return -ENODEV ; + *api_rev = ver; +if (!leds) {dev_warn (dev,"No LEDs found\n" );return -ENODEV ;
@@ -382,7 +409,7 @@ static int nuc_wmi_get_brightness_offset(struct device *dev,if (led->indicator ==LED_IND_DISABLE )return -ENODEV ; - ctrl = led_func_rev_0_64[led->indicator][LED_FUNC_BRIGHTNESS ] + offset; + ctrl = led->reg_table[led->indicator][LED_FUNC_BRIGHTNESS ] + offset;if (!nuc_wmi_test_control (dev, led, ctrl))return -ENODEV ;
@@ -413,7 +440,7 @@ static ssize_t nuc_wmi_set_brightness_offset(struct device *dev,if (led->indicator ==LED_IND_DISABLE )return -ENODEV ; - ctrl = led_func_rev_0_64[led->indicator][LED_FUNC_BRIGHTNESS ] + offset; + ctrl = led->reg_table[led->indicator][LED_FUNC_BRIGHTNESS ] + offset;if (!nuc_wmi_test_control (dev, led, ctrl))return -ENODEV ;
@@ -564,7 +591,7 @@ static ssize_t show_color(struct device *dev,if (led->indicator ==LED_IND_DISABLE )return -ENODEV ; - ctrl = led_func_rev_0_64[led->indicator][LED_FUNC_COLOR1 ]; + ctrl = led->reg_table[led->indicator][LED_FUNC_COLOR1 ];if (!nuc_wmi_test_control (dev, led, ctrl))return -ENODEV ;
@@ -661,7 +688,7 @@ static ssize_t store_color(struct device *dev, } } - ctrl = led_func_rev_0_64[led->indicator][LED_FUNC_COLOR1 ]; + ctrl = led->reg_table[led->indicator][LED_FUNC_COLOR1 ];/* Dual color LEDs */ if (!(led->color_type &LED_RGB )) {
@@ -748,6 +775,8 @@ static ssize_t show_brightness_offset(struct device *dev,if (led->indicator !=LED_IND_POWER_STATE )return -ENODEV ; + offset *= led->reg_table[led->indicator][LED_FUNC_POWER_STATE_NUM_CTRLS ]; + ret =nuc_wmi_get_brightness_offset (dev, led, offset);if (ret <0 )return ret;
@@ -771,6 +800,8 @@ static ssize_t store_brightness_offset(struct device *dev,if (kstrtou8 (buf,0 , &val) || val >100 )return -EINVAL ; + offset *= led->reg_table[led->indicator][LED_FUNC_POWER_STATE_NUM_CTRLS ]; + ret =nuc_wmi_set_brightness_offset (dev, led, offset, val);if (ret)return ret;
@@ -799,13 +830,40 @@ static int nuc_wmi_set_brightness(struct led_classdev *cdev,return nuc_wmi_set_brightness_offset (cdev->dev, led,0 , brightness); } +static umode_t nuc_wmi_led_power_state_is_visible (struct kobject *kobj, +struct attribute *attr, +int idx) +{ +struct device *dev =kobj_to_dev (kobj); +struct led_classdev *cdev =dev_get_drvdata (dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); + +umode_t mode = attr->mode; + +if (!strcmp (attr->name,"s0_brightness" ) || + !strcmp (attr->name,"s3_brightness" )) +return mode; + +if (led->api_rev ==LED_API_REV_0_64 ) { +if (!strcmp (attr->name,"s5_brightness" ) || + !strcmp (attr->name,"ready_mode_brightness" )) +return mode; + }else { +if (!strcmp (attr->name,"standby_brightness" )) +return mode; + } + +return 0 ; +} +static LED_ATTR_RW (indicator);static LED_ATTR_RW (color); -LED_ATTR_POWER_STATE_RW (s0_brightness,0x00 ); -LED_ATTR_POWER_STATE_RW (s3_brightness,0x06 ); -LED_ATTR_POWER_STATE_RW (s5_brightness,0x0c ); -LED_ATTR_POWER_STATE_RW (ready_mode_brightness,0x12 ); +LED_ATTR_POWER_STATE_RW (s0_brightness,0 ); +LED_ATTR_POWER_STATE_RW (s3_brightness,1 ); +LED_ATTR_POWER_STATE_RW (s5_brightness,2 );// Rev 0.64 +LED_ATTR_POWER_STATE_RW (standby_brightness,2 );// Rev 1.0 +LED_ATTR_POWER_STATE_RW (ready_mode_brightness,3 );// Rev 1.0 /* * Attributes for LEDs
@@ -813,15 +871,25 @@ LED_ATTR_POWER_STATE_RW(ready_mode_brightness, 0x12);static struct attribute *nuc_wmi_led_attr[] = { &dev_attr_indicator.attr, +NULL , +}; + +static const struct attribute_group nuc_wmi_led_attribute_group = { + .attrs = nuc_wmi_led_attr, +}; + +static struct attribute *nuc_wmi_led_power_state_attr[] = { &dev_attr_s0_brightness.attr, &dev_attr_s3_brightness.attr, + &dev_attr_standby_brightness.attr, &dev_attr_s5_brightness.attr, &dev_attr_ready_mode_brightness.attr,NULL , }; -static const struct attribute_group nuc_wmi_led_attribute_group = { - .attrs = nuc_wmi_led_attr, +static const struct attribute_group nuc_wmi_led_power_state_group = { + .is_visible = nuc_wmi_led_power_state_is_visible, + .attrs = nuc_wmi_led_power_state_attr, };static struct attribute *nuc_wmi_led_color_attr[] = {
@@ -836,26 +904,36 @@ static const struct attribute_group nuc_wmi_led_color_attribute_group = {static const struct attribute_group *nuc_wmi_led_attribute_groups[] = { &nuc_wmi_led_attribute_group, + &nuc_wmi_led_power_state_group, &nuc_wmi_led_color_attribute_group,NULL }; -static int nuc_wmi_led_register (struct device *dev,struct nuc_nmi_led *led) +static int nuc_wmi_led_register (struct device *dev,struct nuc_nmi_led *led, +enum led_api_rev api_rev) { -int brightness =nuc_wmi_get_brightness_offset (dev, led,0 ); +int brightness; led->cdev.name = led_names[led->id]; led->dev = dev; led->cdev.groups = nuc_wmi_led_attribute_groups; led->cdev.brightness_get = nuc_wmi_get_brightness; led->cdev.brightness_set_blocking = nuc_wmi_set_brightness; + led->api_rev = api_rev; -if (led->color_type &LED_SINGLE_COLOR ) +if (led->color_type &LED_SINGLE_COLOR ) { +if (led->api_rev ==LED_API_REV_1_0 ) + led->reg_table = led_func_rev_1_0_singlecolor; +else + led->reg_table = led_func_multicolor; led->cdev.max_brightness =2 ; -else + }else { led->cdev.max_brightness =100 ; + led->reg_table = led_func_multicolor; + }/* Ensure that the current bright will be preserved */ + brightness =nuc_wmi_get_brightness_offset (dev, led,0 );if (brightness >=0 ) led->cdev.delayed_set_value = brightness;
@@ -868,14 +946,15 @@ static int nuc_wmi_led_register(struct device *dev, struct nuc_nmi_led *led)static int nuc_wmi_leds_setup (struct device *dev) {struct nuc_wmi *priv =dev_get_drvdata (dev); +enum led_api_rev api_rev;int ret, i; - ret =nuc_wmi_query_leds (dev); + ret =nuc_wmi_query_leds (dev, &api_rev);if (ret)return ret;for (i =0 ; i < priv->num_leds; i++) { - ret =nuc_wmi_led_register (dev, &priv->led[i]); + ret =nuc_wmi_led_register (dev, &priv->led[i], api_rev);if (ret) {dev_err (dev,"Failed to register led %d: %s\n" , i, led_names[priv->led[i].id]);
Signature · 1 line
2.31.1
PATCH v2 8/17 leds: leds-nuc: add basic support for NUC6 WMI
In reply to Mauro Carvalho ChehabThe NUC6 and NUCi7 supports an earlier version of the LEDs WMI, as specified at:
https://www.intel.com/content/www/us/en/support/articles/000023426/intel-nuc/intel-nuc-kits.html
Implement the query part of the LED detection for those devices.
Weird enough, at least with Skull Canyon (NUC6i7KYB) using the latest firmware release (KYSKLi70 0071), the WMI call return all zeros. It could well be due to a regression at the Intel's firmware, although this model was not announced as supporting this WMI. At the manufacturer's site, only NUC Kits NUC7i[x]BN and NUC6CAY are mentioned.
Yet, it sounds to me that this is due to a firmware bug:
$ sudo fwts wmi - ... Test 1 of 1: Windows Management Instrumentation test. ...
\_SB_.WMTF._WDG (1 of 1) GUID: 86CCFD48-205E-4A77-9C48-2021CBEDE341 WMI Method: Flags : 0x02 (Method) Object ID : TF Instance : 0x01 Driver : intel-wmi-thunderbolt (Intel) FAILED [LOW] WMIMultipleMethod: Test 1, GUID 86CCFD48-205E-4A77-9C48-2021CBEDE341 has multiple associated methods WMTF defined, this is a firmware bug that leads to ambiguous behaviour. ... \AMW0._WDG (1 of 2) GUID: 8C5DA44C-CDC3-46B3-8619-4E26D34390B7 WMI Method: Flags : 0x02 (Method) Object ID : AA Instance : 0x01 PASSED: Test 1, 8C5DA44C-CDC3-46B3-8619-4E26D34390B7 has associated method \AMW0.WMAA
...
Low failures: 1
wmi: GUID 86CCFD48-205E-4A77-9C48-2021CBEDE341 has multiple associated methods WMTF defined, this is a firmware bug that leads to ambiguous behaviour.
Anyway, this was good enough to test that this patch will be producing exactly the WMI query as the NUC6 OOT driver at:
https://github.com/milesp20/intel_nuc_led/
--- drivers/leds/leds-nuc.c | 142 +++++++++++++++++++++++++++++++--------- 1 file changed, 110 insertions(+), 32 deletions(-)
drivers/leds/leds-nuc.c +110 -32
@@ -8,12 +8,15 @@* * Inspired on WMI from https://github.com/nomego/intel_nuc_led * -* It follows this spec: -* https://www.intel.com/content/dam/support/us/en/documents/intel-nuc/WMI-Spec-Intel-NUC-NUC10ixFNx.pdf +* It follows those specs: +* https://www.intel.com/content/www/us/en/support/articles/000023426/intel-nuc/intel-nuc-kits.html +* https://raw.githubusercontent.com/nomego/intel_nuc_led/master/specs/INTEL_WMI_LED_0.64.pdf +* https://www.intel.com/content/dam/support/us/en/documents/intel-nuc/WMI-Spec-Intel-NUC-NUC10ixFNx.pdf */ #include <linux/acpi.h> #include <linux/bits.h> +#include <linux/dmi.h> #include <linux/kernel.h> #include <linux/leds.h> #include <linux/module.h>
@@ -34,12 +37,21 @@ enum led_api_rev { };enum led_cmds { +/* NUC6-specific cmds */ +LED_OLD_GET_STATUS =0x01 , +LED_OLD_SET_LED =0x02 , + +/* Rev 0.64 and 1.0 cmds */ +LED_QUERY =0x03 ,LED_NEW_GET_STATUS =0x04 ,LED_SET_INDICATOR =0x05 ,LED_SET_VALUE =0x06 ,LED_NOTIFICATION =0x07 ,LED_SWITCH_TYPE =0x08 , + +/* Rev 1.0 cmds */ +LED_VERSION_CONTROL =0x09 , };
@@ -55,6 +67,11 @@ enum led_new_get_subcmd { LED_NEW_GET_CONTROL_ITEM = 0x01, }; +enum led_old_get_subcmd { +LED_OLD_GET_S0_POWER =0x01 , +LED_OLD_GET_S0_RING =0x02 , +}; +enum led_function {LED_FUNC_BRIGHTNESS ,LED_FUNC_COLOR1 ,
@@ -146,14 +163,19 @@ static const u8 led_func_rev_1_0_singlecolor[MAX_IND][MAX_LED_FUNC] = {#define LED_RGB BIT(2)#define LED_SINGLE_COLOR BIT(3) +#define POWER_LED 0 +#define RING_LED (MAX_LEDS + 1) +static const char *const led_names[] = { -"nuc::power" , + [POWER_LED ] ="nuc::power" ,"nuc::hdd" ,"nuc::skull" ,"nuc::eyes" ,"nuc::front1" ,"nuc::front2" ,"nuc::front3" , + + [RING_LED ] ="nuc::ring" ,// NUC6 models };struct nuc_nmi_led {
@@ -276,51 +298,101 @@ static int nuc_nmi_cmd(struct device *dev,return ret; } +static int nuc_wmi_query_leds_nuc6 (struct device *dev) +{ +// FIXME: add a check for the specific models that are known to work +struct nuc_wmi *priv =dev_get_drvdata (dev); +u8 cmd, input[NUM_INPUT_ARGS ] = {0 }; +u8 output[NUM_OUTPUT_ARGS ]; +struct nuc_nmi_led *led; +int ret; + + cmd =LED_OLD_GET_STATUS ; + input[0 ] =LED_OLD_GET_S0_POWER ; + ret =nuc_nmi_cmd (dev, cmd, input, output); +if (ret) { +dev_warn (dev,"Get S0 Power: error %d\n" , ret); +return ret; + } + + led = &priv->led[priv->num_leds]; + led->id =POWER_LED ; + led->color_type =LED_BLUE_AMBER ; + led->avail_indicators =LED_IND_POWER_STATE ; + led->indicator =fls (led->avail_indicators); + priv->num_leds++; + + cmd =LED_OLD_GET_STATUS ; + input[0 ] =LED_OLD_GET_S0_RING ; + ret =nuc_nmi_cmd (dev, cmd, input, output); +if (ret) { +dev_warn (dev,"Get S0 Ring: error %d\n" , ret); +return ret; + } + led = &priv->led[priv->num_leds]; + led->id =RING_LED ; + led->color_type =LED_BLUE_AMBER ; + led->avail_indicators =LED_IND_SOFTWARE ; + led->indicator =fls (led->avail_indicators); + priv->num_leds++; + +return 0 ; +} +static int nuc_wmi_query_leds (struct device *dev,enum led_api_rev *api_rev) {struct nuc_wmi *priv =dev_get_drvdata (dev);u8 input[NUM_INPUT_ARGS ] = {0 };u8 output[NUM_OUTPUT_ARGS ]; -int id, ret, ver =LED_API_UNKNOWN ; +int id, ret, ver =LED_API_UNKNOWN , nuc_ver =0 ;u8 leds; +const char *dmi_name; + + dmi_name =dmi_get_system_info (DMI_PRODUCT_NAME ); +if (!dmi_name || !*dmi_name) + dmi_name =dmi_get_system_info (DMI_BOARD_NAME ); + +if (strncmp (dmi_name,"NUC" ,3 )) +return -ENODEV ; + + dmi_name +=3 ; +while (*dmi_name) { +if (*dmi_name <'0' || *dmi_name >'9' ) +break ; + nuc_ver = (*dmi_name -'0' ) + nuc_ver *10 ; + dmi_name++; + } + +if (nuc_ver <6 ) +return -ENODEV ; + +if (nuc_ver <8 ) { + *api_rev =LED_API_NUC6 ; +return nuc_wmi_query_leds_nuc6 (dev); + } -/* -* List all LED types support in the platform -* -* Should work with both NUC8iXXX and NUC10iXXX -* -* FIXME: Should add a fallback code for it to work with older NUCs, -* as LED_QUERY returns an error on older devices like Skull Canyon. -*/ input[0 ] =LED_QUERY_LIST_ALL ; ret =nuc_nmi_cmd (dev,LED_QUERY , input, output); -if (ret == -ENOENT ) { - ver =LED_API_NUC6 ; - }else if (ret) { +if (ret) {dev_warn (dev,"error %d while listing all LEDs\n" , ret);return ret; - }else { - leds = output[0 ]; } -if (ver !=LED_API_NUC6 ) { - ret =nuc_nmi_cmd (dev,LED_VERSION_CONTROL , input, output); -if (ret) -return ret; + leds = output[0 ]; - ver = output[0 ] | output[1 ] <<16 ; -if (!ver) - ver =LED_API_REV_0_64 ; -else if (ver ==0x0126 ) - ver =LED_API_REV_1_0 ; - } + ret =nuc_nmi_cmd (dev,LED_VERSION_CONTROL , input, output); +if (ret) +return ret; + + ver = output[0 ] | output[1 ] <<16 ; +if (!ver) + *api_rev =LED_API_REV_0_64 ; +else if (ver ==0x0126 ) + *api_rev =LED_API_REV_1_0 ; -/* Currently, only API Revision 0.64 is supported */ -if (ver !=LED_API_REV_0_64 && ver !=LED_API_REV_1_0 ) +if (*api_rev ==LED_API_UNKNOWN )return -ENODEV ; - *api_rev = ver; -if (!leds) {dev_warn (dev,"No LEDs found\n" );return -ENODEV ;
@@ -916,10 +988,16 @@ static int nuc_wmi_led_register(struct device *dev, struct nuc_nmi_led *led, led->cdev.name = led_names[led->id]; led->dev = dev; + led->api_rev = api_rev; + +if (led->api_rev ==LED_API_NUC6 ) { +// FIXME: add NUC6-specific API bits here +return devm_led_classdev_register (dev, &led->cdev); + } + led->cdev.groups = nuc_wmi_led_attribute_groups; led->cdev.brightness_get = nuc_wmi_get_brightness; led->cdev.brightness_set_blocking = nuc_wmi_set_brightness; - led->api_rev = api_rev;if (led->color_type &LED_SINGLE_COLOR ) {if (led->api_rev ==LED_API_REV_1_0 )
Signature · 1 line
2.31.1
PATCH v2 9/17 leds: leds-nuc: add brightness and color for NUC6 API
In reply to Mauro Carvalho ChehabThe NUC6 WMI API is really simple: it has just 2 messages, that retrieves everything for a LED, and it has just 2 LEDs.
Add support for retrieving and set brightness and color.
--- drivers/leds/leds-nuc.c | 198 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 191 insertions(+), 7 deletions(-)
drivers/leds/leds-nuc.c +191 -7
@@ -302,14 +302,13 @@ static int nuc_wmi_query_leds_nuc6(struct device *dev) {// FIXME: add a check for the specific models that are known to work struct nuc_wmi *priv =dev_get_drvdata (dev); -u8 cmd, input[NUM_INPUT_ARGS ] = {0 }; +u8 input[NUM_INPUT_ARGS ] = {0 };u8 output[NUM_OUTPUT_ARGS ];struct nuc_nmi_led *led;int ret; - cmd =LED_OLD_GET_STATUS ; input[0 ] =LED_OLD_GET_S0_POWER ; - ret =nuc_nmi_cmd (dev, cmd, input, output); + ret =nuc_nmi_cmd (dev,LED_OLD_GET_STATUS , input, output);if (ret) {dev_warn (dev,"Get S0 Power: error %d\n" , ret);return ret;
@@ -322,9 +321,8 @@ static int nuc_wmi_query_leds_nuc6(struct device *dev) led->indicator =fls (led->avail_indicators); priv->num_leds++; - cmd =LED_OLD_GET_STATUS ; input[0 ] =LED_OLD_GET_S0_RING ; - ret =nuc_nmi_cmd (dev, cmd, input, output); + ret =nuc_nmi_cmd (dev,LED_OLD_GET_STATUS , input, output);if (ret) {dev_warn (dev,"Get S0 Ring: error %d\n" , ret);return ret;
@@ -547,6 +545,167 @@ static ssize_t nuc_wmi_set_brightness_offset(struct device *dev, } \ static DEVICE_ATTR(_name, 0644, show_##_name, store_##_name) +/* +* NUC6 specific logic +*/ + +static int nuc_wmi_nuc6_led_get_set(struct device *dev, +struct nuc_nmi_led *led,int *brightness, +int *blink_fade,int *color_state) +{ +u8 input[NUM_INPUT_ARGS ] = {0 }; +u8 output[NUM_OUTPUT_ARGS ]; +int ret; + +if (led->id ==POWER_LED ) + input[0 ] =LED_OLD_GET_S0_POWER ; +else + input[0 ] =LED_OLD_GET_S0_RING ; + + ret =nuc_nmi_cmd (dev,LED_OLD_GET_STATUS , input, output); +if (ret) { +dev_warn (dev,"Get %s: error %d\n" , led_names[led->id], ret); +return ret; + } + +if (brightness && *brightness >=0 ) + input[1 ] = *brightness; +else + input[1 ] = output[0 ]; + +if (blink_fade && *blink_fade >=0 ) + input[2 ] = *blink_fade; +else + input[2 ] = output[1 ]; + +if (color_state && *color_state >=0 ) + input[3 ] = *color_state; +else + input[3 ] = output[2 ]; + + ret =nuc_nmi_cmd (dev,LED_OLD_SET_LED , input, output); +if (ret) { +dev_warn (dev,"Get %s: error %d\n" , led_names[led->id], ret); +return ret; + } + +if (brightness) + *brightness = output[0 ]; +if (blink_fade) + *blink_fade = output[1 ]; +if (color_state) + *color_state = output[2 ]; + +return 0 ; +} + +static enum led_brightness nuc_wmi_nuc6_get_brightness (struct led_classdev *cdev) +{ + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); +int ret, brightness =-1 ; + + ret =nuc_wmi_nuc6_led_get_set (cdev->dev, led, &brightness,NULL ,NULL ); +if (ret) +return ret; + +return brightness; +} + +static int nuc_wmi_nuc6_set_brightness (struct led_classdev *cdev, +enum led_brightness bright) +{ +int brightness = bright; + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); + +return nuc_wmi_nuc6_led_get_set (cdev->dev, led, &brightness, +NULL ,NULL ); +} + +static const char *const nuc6_power_colors[] = { +"disable" , +"blue" , +"amber" +}; + +static const char *const nuc6_ring_colors[] = { +"disable" , +"cyan" , +"pink" , +"yellow" , +"blue" , +"red" , +"green" , +"white" +}; + +static ssize_t nuc6_show_color (struct device *dev, +struct device_attribute *attr, +char *buf) +{ +struct led_classdev *cdev =dev_get_drvdata (dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); +int color =-1 , ret, arr_size, i, n; +const char *const *color_names; +int size =PAGE_SIZE ; +char *p = buf; + + ret =nuc_wmi_nuc6_led_get_set (dev, led,NULL ,NULL , &color); +if (ret) +return ret; + +if (led->id ==POWER_LED ) { + color_names = nuc6_power_colors; + arr_size =ARRAY_SIZE (nuc6_power_colors); + }else { + color_names = nuc6_ring_colors; + arr_size =ARRAY_SIZE (nuc6_ring_colors); + } + +for (i =0 ; i < arr_size; i++) { +if (i == color) + n =scnprintf (p, size,"[%s] " , color_names[i]); +else + n =scnprintf (p, size,"%s " , color_names[i]); + p += n; + size -= n; + } + size -=scnprintf (p, size,"\n" ); + +return PAGE_SIZE - size; + +} + +static ssize_t nuc6_store_color (struct device *dev, +struct device_attribute *attr, +const char *buf,size_t len) +{ +struct led_classdev *cdev =dev_get_drvdata (dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); +const char *tmp; +int ret, color; + + tmp =strsep ((char **)&buf,",\n" ); + +if (led->id ==POWER_LED ) { +for (color =ARRAY_SIZE (nuc6_power_colors)+1 ; color >=0 ; color--) +if (!strcasecmp (tmp, nuc6_power_colors[color])) +break ; + }else { +for (color =ARRAY_SIZE (nuc6_ring_colors)+1 ; color >=0 ; color--) +if (!strcasecmp (tmp, nuc6_ring_colors[color])) +break ; + } + +if (color <0 ) +return -EINVAL ; + + ret =nuc_wmi_nuc6_led_get_set (dev, led,NULL ,NULL , &color); +if (ret) +return ret; + +return len; +} +/* Show/change the LED indicator */ static const char *const led_indicators[] = {
@@ -660,6 +819,9 @@ static ssize_t show_color(struct device *dev,char *p = buf;int color, r, g, b; +if (led->api_rev ==LED_API_NUC6 ) +return nuc6_show_color (dev, attr, buf); +if (led->indicator ==LED_IND_DISABLE )return -ENODEV ;
@@ -726,6 +888,9 @@ static ssize_t store_color(struct device *dev,const char *tmp;u8 r, g, b, val; +if (led->api_rev ==LED_API_NUC6 ) +return nuc6_store_color (dev, attr, buf, len); +if (led->indicator ==LED_IND_DISABLE )return -ENODEV ;
@@ -828,6 +993,9 @@ static umode_t nuc_wmi_led_color_is_visible(struct kobject *kobj, struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev);umode_t mode = attr->mode; +if (led->api_rev ==LED_API_NUC6 ) +return mode; +if (led->color_type &LED_SINGLE_COLOR )return 0 ;
@@ -981,17 +1149,33 @@ static const struct attribute_group *nuc_wmi_led_attribute_groups[] = { NULL }; +static const struct attribute_group *nuc_wmi_nuc6_led_attribute_groups[] = { + &nuc_wmi_led_color_attribute_group, +NULL +}; +static int nuc_wmi_led_register (struct device *dev,struct nuc_nmi_led *led,enum led_api_rev api_rev) { -int brightness; +int ret, brightness; led->cdev.name = led_names[led->id]; led->dev = dev; led->api_rev = api_rev;if (led->api_rev ==LED_API_NUC6 ) { -// FIXME: add NUC6-specific API bits here + brightness =-1 ; + ret =nuc_wmi_nuc6_led_get_set (dev, led, &brightness, +NULL ,NULL ); +if (ret) +return ret; + + led->cdev.groups = nuc_wmi_nuc6_led_attribute_groups; + led->cdev.delayed_set_value = brightness; + led->cdev.max_brightness =100 ; + led->cdev.brightness_get = nuc_wmi_nuc6_get_brightness; + led->cdev.brightness_set_blocking = nuc_wmi_nuc6_set_brightness; +return devm_led_classdev_register (dev, &led->cdev); }
Signature · 1 line
2.31.1
PATCH v2 10/17 leds: leds-nuc: Add support to blink behavior for NUC8/10
In reply to Mauro Carvalho ChehabThe hardware blink logic works for both Power State and Software controlled LEDs.
Just like brightness, there is one different blink behavior per different power state. Due to that, the logic is somewhat more complex than what it would be expected otherwise.
--- drivers/leds/leds-nuc.c | 347 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 322 insertions(+), 25 deletions(-)
drivers/leds/leds-nuc.c +322 -25
@@ -530,18 +530,30 @@ static ssize_t nuc_wmi_set_brightness_offset(struct device *dev,#define LED_ATTR_RW (_name) \ DEVICE_ATTR(_name, 0644, show_##_name, store_##_name) -#define LED_ATTR_POWER_STATE_RW (_name, offset) \ +#define LED_ATTR_POWER_STATE_RW (_name, _offname, _offset) \ static ssize_t show_##_name(struct device *dev, \ struct device_attribute *attr, \ char *buf) \ { \ - return show_brightness_offset(dev, attr, offset, buf); \ + struct led_classdev *cdev = dev_get_drvdata(dev); \ + struct nuc_nmi_led *led; \ + \ + led = container_of(cdev, struct nuc_nmi_led, cdev); \ + if (led->indicator != LED_IND_POWER_STATE) \ + return -ENODEV; \ + return offset_show_##_offname(dev, attr, _offset, buf); \ } \ static ssize_t store_##_name(struct device *dev, \ - struct device_attribute *attr, \ - const char *buf, size_t len) \ + struct device_attribute *attr, \ + const char *buf, size_t len) \ { \ - return store_brightness_offset(dev, attr, offset, buf, len); \ + struct led_classdev *cdev = dev_get_drvdata(dev); \ + struct nuc_nmi_led *led; \ + \ + led = container_of(cdev, struct nuc_nmi_led, cdev); \ + if (led->indicator != LED_IND_POWER_STATE) \ + return -ENODEV; \ + return offset_store_##_offname(dev, attr, _offset, buf, len); \ } \ static DEVICE_ATTR(_name, 0644, show_##_name, store_##_name)
@@ -684,7 +696,7 @@ static ssize_t nuc6_store_color(struct device *dev,const char *tmp;int ret, color; - tmp =strsep ((char **)&buf,",\n" ); + tmp =strsep ((char **)&buf,"\n" );if (led->id ==POWER_LED ) {for (color =ARRAY_SIZE (nuc6_power_colors)+1 ; color >=0 ; color--)
@@ -1003,7 +1015,7 @@ static umode_t nuc_wmi_led_color_is_visible(struct kobject *kobj, }/* Show/store brightness */ -static ssize_t show_brightness_offset (struct device *dev, +static ssize_t offset_show_brightness (struct device *dev,struct device_attribute *attr,u8 offset,char *buf)
@@ -1012,9 +1024,6 @@ static ssize_t show_brightness_offset(struct device *dev, struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev);int ret; -if (led->indicator !=LED_IND_POWER_STATE ) -return -ENODEV ; - offset *= led->reg_table[led->indicator][LED_FUNC_POWER_STATE_NUM_CTRLS ]; ret =nuc_wmi_get_brightness_offset (dev, led, offset);
@@ -1024,7 +1033,7 @@ static ssize_t show_brightness_offset(struct device *dev,return scnprintf (buf,PAGE_SIZE ,"%d\n" , ret); } -static ssize_t store_brightness_offset (struct device *dev, +static ssize_t offset_store_brightness (struct device *dev,struct device_attribute *attr,u8 offset,const char *buf,size_t len)
@@ -1034,9 +1043,6 @@ static ssize_t store_brightness_offset(struct device *dev,int ret;u8 val; -if (led->indicator !=LED_IND_POWER_STATE ) -return -ENODEV ; -if (kstrtou8 (buf,0 , &val) || val >100 )return -EINVAL ;
@@ -1070,6 +1076,8 @@ static int nuc_wmi_set_brightness(struct led_classdev *cdev,return nuc_wmi_set_brightness_offset (cdev->dev, led,0 , brightness); } +#define cmp_attr_prefix (a, b) strncmp(a, b, strlen(b)) +static umode_t nuc_wmi_led_power_state_is_visible (struct kobject *kobj,struct attribute *attr,int idx)
@@ -1077,33 +1085,297 @@ static umode_t nuc_wmi_led_power_state_is_visible(struct kobject *kobj,struct device *dev =kobj_to_dev (kobj);struct led_classdev *cdev =dev_get_drvdata (dev); struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); -umode_t mode = attr->mode; -if (!strcmp (attr->name,"s0_brightness" ) || - !strcmp (attr->name,"s3_brightness" )) +if (!cmp_attr_prefix (attr->name,"s0_" ) || + !cmp_attr_prefix (attr->name,"s3_" ))return mode;if (led->api_rev ==LED_API_REV_0_64 ) { -if (!strcmp (attr->name,"s5_brightness" ) || - !strcmp (attr->name,"ready_mode_brightness" )) +if (!cmp_attr_prefix (attr->name,"s5_" ) || + !cmp_attr_prefix (attr->name,"ready_mode_" ))return mode; }else { -if (!strcmp (attr->name,"standby_brightness" )) +if (!cmp_attr_prefix (attr->name,"standby_" ))return mode; }return 0 ; } +/* Blink */ +static const char *const led_blink_behaviors[] = { +"solid" , +"breathing" , +"pulsing" , +"strobing" +}; + +static const char *const led_blink_frequencies[] = { +"0.1" , +"0.2" , +"0.3" , +"0.4" , +"0.5" , +"0.6" , +"0.7" , +"0.8" , +"0.9" , +"1.0" , +}; + +static ssize_t offset_show_blink_behavior (struct device *dev, +struct device_attribute *attr, +u8 offset, +char *buf) +{ +struct led_classdev *cdev =dev_get_drvdata (dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); +u8 input[NUM_INPUT_ARGS ]; +u8 output[NUM_OUTPUT_ARGS ]; +int ret, ctrl, val, i, n; +int size =PAGE_SIZE ; +char *p = buf; + +if (led->indicator ==LED_IND_DISABLE ) +return -ENODEV ; + + offset *= led->reg_table[led->indicator][LED_FUNC_POWER_STATE_NUM_CTRLS ]; + ctrl = led->reg_table[led->indicator][LED_FUNC_BLINK_BEHAVIOR ] + offset; + +if (!nuc_wmi_test_control (dev, led, ctrl)) +return -ENODEV ; + + input[0 ] =LED_NEW_GET_CONTROL_ITEM ; + input[1 ] = led->id; + input[2 ] = led->indicator; + input[3 ] = ctrl; + + ret =nuc_nmi_cmd (dev,LED_NEW_GET_STATUS , input, output); +if (ret) +return ret; + + val = output[0 ]; + +for (i =0 ; i <ARRAY_SIZE (led_blink_behaviors); i++) { +if (i == val) + n =scnprintf (p, size,"[%s] " , led_blink_behaviors[i]); +else + n =scnprintf (p, size,"%s " , led_blink_behaviors[i]); + p += n; + size -= n; + } + size -=scnprintf (p, size,"\n" ); + +return PAGE_SIZE - size; +} + +static ssize_t offset_store_blink_behavior (struct device *dev, +struct device_attribute *attr, +u8 offset, +const char *buf,size_t len) +{ +struct led_classdev *cdev =dev_get_drvdata (dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); +u8 input[NUM_INPUT_ARGS ] = {0 }; +int ctrl, val, ret; +const char *tmp; + +if (led->indicator ==LED_IND_DISABLE ) +return -ENODEV ; + + +if (led->id !=LED_IND_SOFTWARE && led->id !=LED_IND_POWER_STATE ) +return -ENODEV ; + + offset *= led->reg_table[led->indicator][LED_FUNC_POWER_STATE_NUM_CTRLS ]; + ctrl = led->reg_table[led->indicator][LED_FUNC_BLINK_BEHAVIOR ] + offset; + +if (!nuc_wmi_test_control (dev, led, ctrl)) +return -ENODEV ; + + tmp =strsep ((char **)&buf,"\n" ); + +for (val =0 ; val <ARRAY_SIZE (led_blink_behaviors); val++) +if (!strcasecmp (tmp, led_blink_behaviors[val])) +break ; + +if (val >=ARRAY_SIZE (led_blink_behaviors)) +return -EINVAL ; + + input[0 ] = led->id; + input[1 ] = led->indicator; + input[2 ] = ctrl; + input[3 ] = val; + + ret =nuc_nmi_cmd (dev,LED_SET_VALUE , input,NULL ); +if (ret) +return ret; + +return len; +} + +static ssize_t show_blink_behavior (struct device *dev, +struct device_attribute *attr, +char *buf) +{ +return offset_show_blink_behavior (dev, attr,0 , buf); +} + +static ssize_t store_blink_behavior (struct device *dev, +struct device_attribute *attr, +const char *buf,size_t len) +{ +return offset_store_blink_behavior (dev, attr,0 , buf, len); +} + +static ssize_t offset_show_blink_frequency (struct device *dev, +struct device_attribute *attr, +u8 offset, +char *buf) +{ +struct led_classdev *cdev =dev_get_drvdata (dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); +u8 input[NUM_INPUT_ARGS ]; +u8 output[NUM_OUTPUT_ARGS ]; +int ret, ctrl, val, i, n; +int size =PAGE_SIZE ; +char *p = buf; + +if (led->indicator ==LED_IND_DISABLE ) +return -ENODEV ; + + offset *= led->reg_table[led->indicator][LED_FUNC_POWER_STATE_NUM_CTRLS ]; + ctrl = led->reg_table[led->indicator][LED_FUNC_BLINK_BEHAVIOR ] + offset; + +if (!nuc_wmi_test_control (dev, led, ctrl)) +return -ENODEV ; + + input[0 ] =LED_NEW_GET_CONTROL_ITEM ; + input[1 ] = led->id; + input[2 ] = led->indicator; + input[3 ] = ctrl; + + ret =nuc_nmi_cmd (dev,LED_NEW_GET_STATUS , input, output); +if (ret) +return ret; + + val = output[0 ]; + +for (i =0 ; i <ARRAY_SIZE (led_blink_frequencies); i++) { +if (i == val) + n =scnprintf (p, size,"[%s] " , led_blink_frequencies[i]); +else + n =scnprintf (p, size,"%s " , led_blink_frequencies[i]); + p += n; + size -= n; + } + size -=scnprintf (p, size,"\n" ); + +return PAGE_SIZE - size; +} + +static ssize_t offset_store_blink_frequency (struct device *dev, +struct device_attribute *attr, +u8 offset, +const char *buf,size_t len) +{ +struct led_classdev *cdev =dev_get_drvdata (dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); +u8 input[NUM_INPUT_ARGS ] = {0 }; +int ctrl, val, ret; +const char *tmp; + +if (led->indicator ==LED_IND_DISABLE ) +return -ENODEV ; + + +if (led->id !=LED_IND_SOFTWARE && led->id !=LED_IND_POWER_STATE ) +return -ENODEV ; + + offset *= led->reg_table[led->indicator][LED_FUNC_POWER_STATE_NUM_CTRLS ]; + ctrl = led->reg_table[led->indicator][LED_FUNC_BLINK_BEHAVIOR ] + offset; + +if (!nuc_wmi_test_control (dev, led, ctrl)) +return -ENODEV ; + + tmp =strsep ((char **)&buf,"\n" ); + +for (val =0 ; val <ARRAY_SIZE (led_blink_frequencies); val++) +if (!strcasecmp (tmp, led_blink_frequencies[val])) +break ; + +if (val >=ARRAY_SIZE (led_blink_frequencies)) +return -EINVAL ; + + input[0 ] = led->id; + input[1 ] = led->indicator; + input[2 ] = ctrl; + input[3 ] = val +1 ; + + ret =nuc_nmi_cmd (dev,LED_SET_VALUE , input,NULL ); +if (ret) +return ret; + +return len; +} + +static ssize_t show_blink_frequency (struct device *dev, +struct device_attribute *attr, +char *buf) +{ +return offset_show_blink_frequency (dev, attr,0 , buf); +} + +static ssize_t store_blink_frequency (struct device *dev, +struct device_attribute *attr, +const char *buf,size_t len) +{ +return offset_store_blink_frequency (dev, attr,0 , buf, len); +} + +static umode_t nuc_wmi_led_blink_is_visible (struct kobject *kobj, +struct attribute *attr,int idx) +{ +struct device *dev =kobj_to_dev (kobj); +struct led_classdev *cdev =dev_get_drvdata (dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); +umode_t mode = attr->mode; + +// TODO: implement for NUC6 API +if (led->api_rev ==LED_API_NUC6 ) +return 0 ; + +if (led->id ==LED_IND_SOFTWARE ) +return mode; + +return 0 ; +} +static LED_ATTR_RW (indicator);static LED_ATTR_RW (color); +static LED_ATTR_RW (blink_behavior); +static LED_ATTR_RW (blink_frequency); -LED_ATTR_POWER_STATE_RW (s0_brightness,0 ); -LED_ATTR_POWER_STATE_RW (s3_brightness,1 ); -LED_ATTR_POWER_STATE_RW (s5_brightness,2 );// Rev 0.64 -LED_ATTR_POWER_STATE_RW (standby_brightness,2 );// Rev 1.0 -LED_ATTR_POWER_STATE_RW (ready_mode_brightness,3 );// Rev 1.0 +LED_ATTR_POWER_STATE_RW (s0_brightness, brightness,0 ); +LED_ATTR_POWER_STATE_RW (s0_blink_behavior, blink_behavior,0 ); +LED_ATTR_POWER_STATE_RW (s0_blink_frequency, blink_frequency,0 ); +LED_ATTR_POWER_STATE_RW (s3_brightness, brightness,1 ); +LED_ATTR_POWER_STATE_RW (s3_blink_behavior, blink_behavior,1 ); +LED_ATTR_POWER_STATE_RW (s3_blink_frequency, blink_frequency,1 ); + +/* Rev 0.64 */ +LED_ATTR_POWER_STATE_RW (s5_brightness, brightness,2 ); +LED_ATTR_POWER_STATE_RW (s5_blink_behavior, blink_behavior,2 ); +LED_ATTR_POWER_STATE_RW (s5_blink_frequency, blink_frequency,2 ); +LED_ATTR_POWER_STATE_RW (ready_mode_brightness, brightness,3 ); +LED_ATTR_POWER_STATE_RW (ready_mode_blink_behavior, blink_behavior,3 ); +LED_ATTR_POWER_STATE_RW (ready_mode_blink_frequency, blink_frequency,3 ); + +/* Rev 1.0 */ +LED_ATTR_POWER_STATE_RW (standby_brightness, brightness,2 ); +LED_ATTR_POWER_STATE_RW (standby_blink_behavior, blink_behavior,2 ); +LED_ATTR_POWER_STATE_RW (standby_blink_frequency, blink_frequency,2 );/* * Attributes for LEDs
@@ -1124,6 +1396,19 @@ static struct attribute *nuc_wmi_led_power_state_attr[] = { &dev_attr_standby_brightness.attr, &dev_attr_s5_brightness.attr, &dev_attr_ready_mode_brightness.attr, + + &dev_attr_s0_blink_behavior.attr, + &dev_attr_s3_blink_behavior.attr, + &dev_attr_standby_blink_behavior.attr, + &dev_attr_s5_blink_behavior.attr, + &dev_attr_ready_mode_blink_behavior.attr, + + &dev_attr_s0_blink_frequency.attr, + &dev_attr_s3_blink_frequency.attr, + &dev_attr_standby_blink_frequency.attr, + &dev_attr_s5_blink_frequency.attr, + &dev_attr_ready_mode_blink_frequency.attr, + NULL, };
@@ -1142,10 +1427,22 @@ static const struct attribute_group nuc_wmi_led_color_attribute_group = { .attrs = nuc_wmi_led_color_attr, }; +static struct attribute *nuc_wmi_led_blink_behavior_attr[] = { + &dev_attr_blink_behavior.attr, + &dev_attr_blink_frequency.attr, +NULL , +}; + +static const struct attribute_group nuc_wmi_led_blink_attribute_group = { + .is_visible = nuc_wmi_led_blink_is_visible, + .attrs = nuc_wmi_led_blink_behavior_attr, +}; +static const struct attribute_group *nuc_wmi_led_attribute_groups[] = { &nuc_wmi_led_attribute_group, &nuc_wmi_led_power_state_group, &nuc_wmi_led_color_attribute_group, + &nuc_wmi_led_blink_attribute_group,NULL };
Signature · 1 line
2.31.1
Re: [PATCH v2 10/17] leds: leds-nuc: Add support to blink behavior for NUC8/10
This should be implemented via the blink trigger or hw_pattern, I think. Have you looked at these?
Marek
Re: [PATCH v2 10/17] leds: leds-nuc: Add support to blink behavior for NUC8/10
Em Wed, 19 May 2021 09:58:50 +0200
Marek Behun <[email protected]> escreveu:
Quoted 2 lines
This should be implemented via the blink trigger or
hw_pattern, I think. Have you looked at these?
The blink trigger and hw_pattern are software implementations for blink.
This is not the case on NUC leds, as:
1. It is the hardware/firmware that triggers the blink, not Linux;
2. It blinks even when Linux in suspend/hibernate mode.
In a matter of fact, the default usage of blink is to indicate that the machine is suspended.
Thanks,
Mauro
PATCH v2 11/17 leds: leds-nuc: get rid of an unused variable
In reply to Mauro Carvalho Chehabdrivers/staging/nuc-led/nuc-wmi.c: In function ‘nuc_nmi_cmd’: drivers/staging/nuc-led/nuc-wmi.c:242:6: warning: variable ‘size’ set but not used [-Wunused-but-set-variable] 242 | int size, ret; | ^~~~
--- drivers/leds/leds-nuc.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-)
drivers/leds/leds-nuc.c +1 -3
@@ -239,7 +239,7 @@ static int nuc_nmi_cmd(struct device *dev,struct acpi_buffer input;union acpi_object *obj;acpi_status status; -int size, ret; +int ret;u8 *p; input.length =NUM_INPUT_ARGS ;
@@ -281,8 +281,6 @@ static int nuc_nmi_cmd(struct device *dev, goto err; } - size =NUM_OUTPUT_ARGS +1 ; -if (output_args) {memcpy (output_args, p +1 ,NUM_OUTPUT_ARGS );
Signature · 1 line
2.31.1
PATCH v2 12/17 leds: leds-nuc: implement blink control for NUC6
In reply to Mauro Carvalho ChehabThe blink control logic for NUC6 API is somewhat messy, as it uses a single register for controlling both the blink type and the frequency, using a random order.
Let's use the same API as defined for other versions, splitting this setting on two different properties.
--- drivers/leds/leds-nuc.c | 269 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 267 insertions(+), 2 deletions(-)
drivers/leds/leds-nuc.c +267 -2
@@ -716,6 +716,247 @@ static ssize_t nuc6_store_color(struct device *dev,return len; } +enum nuc6_blink_mode_freq { +NUC6_BLINK_MODE_BLINK_1HZ =0x01 , +NUC6_BLINK_MODE_BLINK_0_25HZ =0x02 , +NUC6_BLINK_MODE_FADE_1HZ =0x03 , +NUC6_BLINK_MODE_DONT_BLINK =0x04 , + +/* BIOS equal or upper AY0038 or BN0043 */ +NUC6_BLINK_MODE_BLINK_0_5HZ =0x05 , +NUC6_BLINK_MODE_FADE_0_25HZ =0x06 , +NUC6_BLINK_MODE_FADE_0_5HZ =0x07 +}; + +enum nuc6_blink_mode { +NUC6_BLINK_MODE_SOLID , +NUC6_BLINK_MODE_BLINK , +NUC6_BLINK_MODE_FADE +}; + +static const char *const nuc6_blink_behavior[] = { +"solid" , +"blink" , +"fade" , +}; + +enum nuc6_blink_freq { +NUC6_BLINK_FREQ_1HZ , +NUC6_BLINK_FREQ_0_5HZ , +NUC6_BLINK_FREQ_0_25HZ , +}; + +static const char *const nuc6_blink_frequency[] = { +"1" , +"0.5" , +"0.25" , +}; + +static int nuc_wmi_nuc6_set_blink (struct device *dev, +struct nuc_nmi_led *led, +int freq,enum nuc6_blink_mode mode) +{ +int val; + +switch (mode) { +case NUC6_BLINK_MODE_SOLID : + val =NUC6_BLINK_MODE_DONT_BLINK ; +break ; +case NUC6_BLINK_MODE_BLINK : +if (freq ==NUC6_BLINK_FREQ_0_25HZ ) + val =NUC6_BLINK_MODE_BLINK_0_25HZ ; +else if (freq ==NUC6_BLINK_FREQ_0_5HZ ) + val =NUC6_BLINK_MODE_BLINK_0_5HZ ; +else + val =NUC6_BLINK_MODE_BLINK_1HZ ; +break ; +case NUC6_BLINK_MODE_FADE : +if (freq ==NUC6_BLINK_FREQ_0_25HZ ) + val =NUC6_BLINK_MODE_FADE_0_25HZ ; +else if (freq ==NUC6_BLINK_FREQ_0_5HZ ) + val =NUC6_BLINK_MODE_FADE_0_5HZ ; +else + val =NUC6_BLINK_MODE_FADE_1HZ ; +break ; +default : +return -EINVAL ; + } + +return nuc_wmi_nuc6_led_get_set (dev, led,NULL , &val,NULL ); +} + +static ssize_t nuc6_show_blink_behavior (struct device *dev, +struct device_attribute *attr, +char *buf) +{ +struct led_classdev *cdev =dev_get_drvdata (dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); +int val =-1 , mode =-1 , ret, i, n; +int size =PAGE_SIZE ; +char *p = buf; + + ret =nuc_wmi_nuc6_led_get_set (dev, led,NULL , &val,NULL ); +if (ret) +return ret; + +switch (val) { +case NUC6_BLINK_MODE_BLINK_1HZ : +case NUC6_BLINK_MODE_BLINK_0_25HZ : +case NUC6_BLINK_MODE_BLINK_0_5HZ : + mode =NUC6_BLINK_MODE_BLINK ; +break ; +case NUC6_BLINK_MODE_FADE_1HZ : +case NUC6_BLINK_MODE_FADE_0_25HZ : +case NUC6_BLINK_MODE_FADE_0_5HZ : + mode =NUC6_BLINK_MODE_FADE ; +break ; +case NUC6_BLINK_MODE_DONT_BLINK : + mode =NUC6_BLINK_MODE_SOLID ; +break ; + } + +for (i =0 ; i <ARRAY_SIZE (nuc6_blink_behavior); i++) { +if (i == mode) + n =scnprintf (p, size,"[%s] " , nuc6_blink_behavior[i]); +else + n =scnprintf (p, size,"%s " , nuc6_blink_behavior[i]); + p += n; + size -= n; + } + size -=scnprintf (p, size,"\n" ); + +return PAGE_SIZE - size; + +} + +static ssize_t nuc6_store_blink_behavior (struct device *dev, +struct device_attribute *attr, +const char *buf,size_t len) +{ +struct led_classdev *cdev =dev_get_drvdata (dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); +int ret, val =-1 , freq; +const char *tmp; + + tmp =strsep ((char **)&buf,"\n" ); + + ret =nuc_wmi_nuc6_led_get_set (dev, led,NULL , &val,NULL ); +if (ret) +return ret; + +/* Preserve the frequency */ +switch (val) { +case NUC6_BLINK_MODE_BLINK_0_25HZ : +case NUC6_BLINK_MODE_FADE_0_25HZ : + freq =NUC6_BLINK_FREQ_0_25HZ ; +break ; +case NUC6_BLINK_MODE_BLINK_0_5HZ : +case NUC6_BLINK_MODE_FADE_0_5HZ : + freq =NUC6_BLINK_FREQ_0_5HZ ; +break ; +default : + freq =NUC6_BLINK_FREQ_1HZ ; +break ; + } + +for (val =ARRAY_SIZE (nuc6_blink_behavior)+1 ; val >=0 ; val--) +if (!strcasecmp (tmp, nuc6_blink_behavior[val])) +break ; +if (val <0 ) +return -EINVAL ; + + ret =nuc_wmi_nuc6_set_blink (dev, led, val, freq); +if (ret) +return ret; + +return len; +} + +static ssize_t nuc6_show_blink_frequency (struct device *dev, +struct device_attribute *attr, +char *buf) +{ +struct led_classdev *cdev =dev_get_drvdata (dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); +int val =-1 , freq =-1 , ret, i, n; +int size =PAGE_SIZE ; +char *p = buf; + + ret =nuc_wmi_nuc6_led_get_set (dev, led,NULL , &val,NULL ); +if (ret) +return ret; + +switch (val) { +case NUC6_BLINK_MODE_BLINK_0_25HZ : +case NUC6_BLINK_MODE_FADE_0_25HZ : + freq =NUC6_BLINK_FREQ_0_25HZ ; +break ; +case NUC6_BLINK_MODE_BLINK_0_5HZ : +case NUC6_BLINK_MODE_FADE_0_5HZ : + freq =NUC6_BLINK_FREQ_0_5HZ ; +break ; +default : + freq =NUC6_BLINK_FREQ_1HZ ; + } + +for (i =0 ; i <ARRAY_SIZE (nuc6_blink_frequency); i++) { +if (i == freq) + n =scnprintf (p, size,"[%s] " , nuc6_blink_frequency[i]); +else + n =scnprintf (p, size,"%s " , nuc6_blink_frequency[i]); + p += n; + size -= n; + } + size -=scnprintf (p, size,"\n" ); + +return PAGE_SIZE - size; +} + +static ssize_t nuc6_store_blink_frequency (struct device *dev, +struct device_attribute *attr, +const char *buf,size_t len) +{ +struct led_classdev *cdev =dev_get_drvdata (dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); +enum nuc6_blink_mode mode; +int ret, freq, val =-1 ; +const char *tmp; + + tmp =strsep ((char **)&buf,"\n" ); + + ret =nuc_wmi_nuc6_led_get_set (dev, led,NULL , &val,NULL ); +if (ret) +return ret; + +/* Preserve the blink mode */ +switch (val) { +case NUC6_BLINK_MODE_BLINK_1HZ : +case NUC6_BLINK_MODE_BLINK_0_25HZ : +case NUC6_BLINK_MODE_BLINK_0_5HZ : + mode =NUC6_BLINK_MODE_BLINK ; +break ; +case NUC6_BLINK_MODE_FADE_1HZ : +case NUC6_BLINK_MODE_FADE_0_25HZ : +case NUC6_BLINK_MODE_FADE_0_5HZ : + mode =NUC6_BLINK_MODE_FADE ; +break ; +default :/* setting frequency NUC6_BLINK_MODE_SOLID won't make sense */ +return -EINVAL ; + } + +for (freq =ARRAY_SIZE (nuc6_blink_frequency)+1 ; freq >=0 ; freq--) +if (!strcasecmp (tmp, nuc6_blink_frequency[freq])) +break ; +if (freq <0 ) +return -EINVAL ; + + ret =nuc_wmi_nuc6_set_blink (dev, led, mode, freq); +if (ret) +return ret; + +return len; +} +/* Show/change the LED indicator */ static const char *const led_indicators[] = {
@@ -1217,6 +1458,12 @@ static ssize_t show_blink_behavior(struct device *dev,struct device_attribute *attr, char *buf) { + struct led_classdev *cdev = dev_get_drvdata(dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); + +if (led->api_rev ==LED_API_NUC6 ) +return nuc6_show_blink_behavior (dev, attr, buf); +return offset_show_blink_behavior (dev, attr,0 , buf); }
@@ -1224,6 +1471,12 @@ static ssize_t store_blink_behavior(struct device *dev,struct device_attribute *attr, const char *buf, size_t len) { + struct led_classdev *cdev = dev_get_drvdata(dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); + +if (led->api_rev ==LED_API_NUC6 ) +return nuc6_store_blink_behavior (dev, attr, buf, len); +return offset_store_blink_behavior (dev, attr,0 , buf, len); }
@@ -1322,6 +1575,12 @@ static ssize_t show_blink_frequency(struct device *dev,struct device_attribute *attr, char *buf) { + struct led_classdev *cdev = dev_get_drvdata(dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); + +if (led->api_rev ==LED_API_NUC6 ) +return nuc6_show_blink_frequency (dev, attr, buf); +return offset_show_blink_frequency (dev, attr,0 , buf); }
@@ -1329,6 +1588,12 @@ static ssize_t store_blink_frequency(struct device *dev,struct device_attribute *attr, const char *buf, size_t len) { + struct led_classdev *cdev = dev_get_drvdata(dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); + +if (led->api_rev ==LED_API_NUC6 ) +return nuc6_store_blink_frequency (dev, attr, buf, len); +return offset_store_blink_frequency (dev, attr,0 , buf, len); }
@@ -1340,9 +1605,8 @@ static umode_t nuc_wmi_led_blink_is_visible(struct kobject *kobj, struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev);umode_t mode = attr->mode; -// TODO: implement for NUC6 API if (led->api_rev ==LED_API_NUC6 ) -return 0 ; +return mode;if (led->id ==LED_IND_SOFTWARE )return mode;
@@ -1446,6 +1710,7 @@ static const struct attribute_group *nuc_wmi_led_attribute_groups[] = {static const struct attribute_group *nuc_wmi_nuc6_led_attribute_groups[] = { &nuc_wmi_led_color_attribute_group, + &nuc_wmi_led_blink_attribute_group,NULL };
Signature · 1 line
2.31.1
PATCH v2 13/17 leds: leds-nuc: better detect NUC6/NUC7 devices
In reply to Mauro Carvalho ChehabThere's no documented way to detect if the WMI API is valid, as, when it is not valid, it just returns 4 zeros.
However, as having a value of 0x00 for the blinking state is not valid, we can check for it at detection time, in order to disable LEDs control on devices that won't support it.
--- drivers/leds/leds-nuc.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+)
drivers/leds/leds-nuc.c +15 -0
@@ -312,6 +312,13 @@ static int nuc_wmi_query_leds_nuc6(struct device *dev)return ret; } +/* +* Detect if NUC6/NUC7 supports the WMI API by checking the +* returned blink state, as valid values range from 0x01 to 0x07. +*/ +if (output[1 ] ==0x00 ) +return -ENODEV ; + led = &priv->led[priv->num_leds]; led->id =POWER_LED ; led->color_type =LED_BLUE_AMBER ;
@@ -325,6 +332,14 @@ static int nuc_wmi_query_leds_nuc6(struct device *dev)dev_warn (dev,"Get S0 Ring: error %d\n" , ret);return ret; } + +/* +* Detect if NUC6/NUC7 supports the WMI API by checking the +* returned blink state, as valid values range from 0x01 to 0x07. +*/ +if (output[1 ] ==0x00 ) +return -ENODEV ; + led = &priv->led[priv->num_leds]; led->id =RING_LED ; led->color_type =LED_BLUE_AMBER ;
Signature · 1 line
2.31.1
PATCH v2 14/17 leds: leds-nuc: add support for HDD activity default
In reply to Mauro Carvalho ChehabThere are two possible values for HDD activity behavior:
- 0 Normally off, ON when active
- 1 Normally on, OFF when active
Implement a logic to set it.
--- drivers/leds/leds-nuc.c | 77 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+)
drivers/leds/leds-nuc.c +77 -0
@@ -1629,10 +1629,86 @@ static umode_t nuc_wmi_led_blink_is_visible(struct kobject *kobj,return 0 ; } +/* HDD activity behavior */ +static ssize_t show_hdd_default (struct device *dev, +struct device_attribute *attr, +char *buf) +{ +struct led_classdev *cdev =dev_get_drvdata (dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); +u8 input[NUM_INPUT_ARGS ] = {0 }; +u8 output[NUM_OUTPUT_ARGS ]; +int ctrl, ret, val; + +if (led->indicator !=LED_IND_HDD_ACTIVITY ) +return -EINVAL ; + + ctrl = led->reg_table[led->indicator][LED_FUNC_HDD_BEHAVIOR ]; + +if (!nuc_wmi_test_control (dev, led, ctrl)) +return -ENODEV ; + + input[0 ] =LED_NEW_GET_CONTROL_ITEM ; + input[1 ] = led->id; + input[2 ] = led->indicator; + input[3 ] = ctrl; + + ret =nuc_nmi_cmd (dev,LED_NEW_GET_STATUS , input, output); +if (ret) +return ret; + + val = output[0 ]; + +if (val ==0 ) +return scnprintf (buf,PAGE_SIZE ,"off\n" ); + +return scnprintf (buf,PAGE_SIZE ,"on\n" ); +} + +static ssize_t store_hdd_default (struct device *dev, +struct device_attribute *attr, +const char *buf,size_t len) +{ +struct led_classdev *cdev =dev_get_drvdata (dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); +u8 input[NUM_INPUT_ARGS ] = {0 }; +int ctrl, val, ret; +const char *tmp; + +if (led->indicator !=LED_IND_HDD_ACTIVITY ) +return -EINVAL ; + + ctrl = led->reg_table[led->indicator][LED_FUNC_HDD_BEHAVIOR ]; + +if (!nuc_wmi_test_control (dev, led, ctrl)) +return -ENODEV ; + + tmp =strsep ((char **)&buf,"\n" ); +if (!strcmp (tmp,"on" )) + val =1 ; +else if (!strcmp (tmp,"off" )) + val =0 ; +else +return -EINVAL ; + + input[0 ] = led->id; + input[1 ] = led->indicator; + input[2 ] = ctrl; + input[3 ] = val; + + ret =nuc_nmi_cmd (dev,LED_SET_VALUE , input,NULL ); +if (ret) +return ret; + +return len; +} + +static LED_ATTR_RW (indicator);static LED_ATTR_RW (color);static LED_ATTR_RW (blink_behavior);static LED_ATTR_RW (blink_frequency); +static LED_ATTR_RW (hdd_default);LED_ATTR_POWER_STATE_RW (s0_brightness, brightness,0 );LED_ATTR_POWER_STATE_RW (s0_blink_behavior, blink_behavior,0 );
@@ -1660,6 +1736,7 @@ LED_ATTR_POWER_STATE_RW(standby_blink_frequency, blink_frequency, 2);static struct attribute *nuc_wmi_led_attr[] = { &dev_attr_indicator.attr, + &dev_attr_hdd_default.attr,NULL , };
Signature · 1 line
2.31.1
PATCH v2 15/17 leds: leds-nuc: fix software blink behavior logic
In reply to Mauro Carvalho ChehabThe is_visible logic for it is plain wrong:
1. it is used only during devnode creation;
2. it was using the wrong field (id, instead of indicator).
Fix it.
--- drivers/leds/leds-nuc.c | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-)
drivers/leds/leds-nuc.c +12 -18
@@ -1479,6 +1479,9 @@ static ssize_t show_blink_behavior(struct device *dev,if (led->api_rev ==LED_API_NUC6 )return nuc6_show_blink_behavior (dev, attr, buf); +if (led->indicator !=LED_IND_SOFTWARE ) +return -EINVAL ; +return offset_show_blink_behavior (dev, attr,0 , buf); }
@@ -1492,6 +1495,9 @@ static ssize_t store_blink_behavior(struct device *dev,if (led->api_rev ==LED_API_NUC6 )return nuc6_store_blink_behavior (dev, attr, buf, len); +if (led->indicator !=LED_IND_SOFTWARE ) +return -EINVAL ; +return offset_store_blink_behavior (dev, attr,0 , buf, len); }
@@ -1596,6 +1602,9 @@ static ssize_t show_blink_frequency(struct device *dev,if (led->api_rev ==LED_API_NUC6 )return nuc6_show_blink_frequency (dev, attr, buf); +if (led->indicator !=LED_IND_SOFTWARE ) +return -EINVAL ; +return offset_show_blink_frequency (dev, attr,0 , buf); }
@@ -1609,26 +1618,12 @@ static ssize_t store_blink_frequency(struct device *dev,if (led->api_rev ==LED_API_NUC6 )return nuc6_store_blink_frequency (dev, attr, buf, len); +if (led->indicator !=LED_IND_SOFTWARE ) +return -EINVAL ; +return offset_store_blink_frequency (dev, attr,0 , buf, len); } -static umode_t nuc_wmi_led_blink_is_visible (struct kobject *kobj, -struct attribute *attr,int idx) -{ -struct device *dev =kobj_to_dev (kobj); -struct led_classdev *cdev =dev_get_drvdata (dev); - struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); -umode_t mode = attr->mode; - -if (led->api_rev ==LED_API_NUC6 ) -return mode; - -if (led->id ==LED_IND_SOFTWARE ) -return mode; - -return 0 ; -} -/* HDD activity behavior */ static ssize_t show_hdd_default(struct device *dev, struct device_attribute *attr,
@@ -1788,7 +1783,6 @@ static struct attribute *nuc_wmi_led_blink_behavior_attr[] = { };static const struct attribute_group nuc_wmi_led_blink_attribute_group = { - .is_visible = nuc_wmi_led_blink_is_visible, .attrs = nuc_wmi_led_blink_behavior_attr, };
Signature · 1 line
2.31.1
PATCH v2 16/17 leds: leds-nuc: add support for changing the ethernet type indicator
In reply to Mauro Carvalho ChehabThe Ethernet type indicator can be configured to show the status of LAN1, LAN1 or both. Add support for it.
--- drivers/leds/leds-nuc.c | 89 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+)
drivers/leds/leds-nuc.c +89 -0
@@ -1698,12 +1698,100 @@ static ssize_t store_hdd_default(struct device *dev,return len; } +/* Ethernet type */ +static const char *const ethernet_type[] = { +"LAN1" , +"LAN2" , +"LAN1+LAN2" +}; + +static ssize_t show_ethernet_type (struct device *dev, +struct device_attribute *attr, +char *buf) +{ +struct led_classdev *cdev =dev_get_drvdata (dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); +u8 input[NUM_INPUT_ARGS ] = {0 }; +u8 output[NUM_OUTPUT_ARGS ]; +int ctrl, ret, val, i, n; +int size =PAGE_SIZE ; +char *p = buf; + +if (led->indicator !=LED_IND_ETHERNET ) +return -EINVAL ; + + ctrl = led->reg_table[led->indicator][LED_FUNC_ETH_TYPE ]; + +if (!nuc_wmi_test_control (dev, led, ctrl)) +return -ENODEV ; + + input[0 ] =LED_NEW_GET_CONTROL_ITEM ; + input[1 ] = led->id; + input[2 ] = led->indicator; + input[3 ] = ctrl; + + ret =nuc_nmi_cmd (dev,LED_NEW_GET_STATUS , input, output); +if (ret) +return ret; + + val = output[0 ]; + +for (i =0 ; i <ARRAY_SIZE (ethernet_type); i++) { +if (i == val) + n =scnprintf (p, size,"[%s] " , ethernet_type[i]); +else + n =scnprintf (p, size,"%s " , ethernet_type[i]); + p += n; + size -= n; + } + size -=scnprintf (p, size,"\n" ); + +return PAGE_SIZE - size; +} + +static ssize_t store_ethernet_type (struct device *dev, +struct device_attribute *attr, +const char *buf,size_t len) +{ +struct led_classdev *cdev =dev_get_drvdata (dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); +u8 input[NUM_INPUT_ARGS ] = {0 }; +int ctrl, val, ret; +const char *tmp; + +if (led->indicator !=LED_IND_ETHERNET ) +return -EINVAL ; + + ctrl = led->reg_table[led->indicator][LED_FUNC_ETH_TYPE ]; + +if (!nuc_wmi_test_control (dev, led, ctrl)) +return -ENODEV ; + +for (val =0 ; val <ARRAY_SIZE (ethernet_type); val++) +if (!strcasecmp (tmp, ethernet_type[val])) +break ; + +if (val >=ARRAY_SIZE (ethernet_type)) +return -EINVAL ; + + input[0 ] = led->id; + input[1 ] = led->indicator; + input[2 ] = ctrl; + input[3 ] = val; + + ret =nuc_nmi_cmd (dev,LED_SET_VALUE , input,NULL ); +if (ret) +return ret; + +return len; +}static LED_ATTR_RW (indicator);static LED_ATTR_RW (color);static LED_ATTR_RW (blink_behavior);static LED_ATTR_RW (blink_frequency);static LED_ATTR_RW (hdd_default); +static LED_ATTR_RW (ethernet_type);LED_ATTR_POWER_STATE_RW (s0_brightness, brightness,0 );LED_ATTR_POWER_STATE_RW (s0_blink_behavior, blink_behavior,0 );
@@ -1732,6 +1820,7 @@ LED_ATTR_POWER_STATE_RW(standby_blink_frequency, blink_frequency, 2);static struct attribute *nuc_wmi_led_attr[] = { &dev_attr_indicator.attr, &dev_attr_hdd_default.attr, + &dev_attr_ethernet_type.attr,NULL , };
Signature · 1 line
2.31.1
Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
What possible configurations does this support?
Does this blink on rx/tx activity for a specific ethernet port?
There is a work in progress to add support for transparent offloading of LED triggers, with the netdev trigger being the first target.
This means that after that is done, you could implement this driver so that when netdev trigger is enabled on a supported interface, your driver will offload the blinking to the HW.
This should probably also work for HDD activity, but this would need a blockdev trigger first...
Marek
Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
Em Wed, 19 May 2021 10:02:53 +0200
Marek Behún <[email protected]> escreveu:
Quoted 4 lines
What possible configurations does this support?
Does this blink on rx/tx activity for a specific ethernet port?
When the indicator is set to monitor Ethernet, it can work on either LAN1, LAN2 or both LAN interfaces.
Quoted 6 lines
There is a work in progress to add support for transparent offloading of LED triggers, with the netdev trigger being the first target.
This means that after that is done, you could implement this driver so that when netdev trigger is enabled on a supported interface, your driver will offload the blinking to the HW.
On NUC leds, this is already offloaded to HW/firmware.
All it takes is to tell the BIOS via ACPI/WMI what LED will be used for monitoring the Ethernet activity, and on what port(s).
Quoted 2 lines
This should probably also work for HDD activity, but this would need a blockdev trigger first...
HDD activity is also HW/firmware monitored. The only thing the Kernel needs to to is to select what LED will be set as the HDD activity indicator.
Thanks,
Mauro
Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
Quoted 14 lines · On Wed, 19 May 2021 12:18:12 +0200 Mauro Carvalho Chehab…
Em Wed, 19 May 2021 10:02:53 +0200
Marek Behún <[email protected]> escreveu:Quoted 4 lines
What possible configurations does this support?
Does this blink on rx/tx activity for a specific ethernet port?
When the indicator is set to monitor Ethernet, it can work on either LAN1, LAN2 or both LAN interfaces.
Quoted 3 lines
There is a work in progress to add support for transparent offloading of LED triggers, with the netdev trigger being the first target.
Quoted 8 lines
Quoted 3 lines
This means that after that is done, you could implement this driver so that when netdev trigger is enabled on a supported interface, your driver will offload the blinking to the HW.
On NUC leds, this is already offloaded to HW/firmware.
All it takes is to tell the BIOS via ACPI/WMI what LED will be used for monitoring the Ethernet activity, and on what port(s).
Can the LED be put into software controlled mode and also into HW controlled mode so that HW blinks the LED on ethernet activity?
If so, then this is what I am talking about: transparent HW offloading of LED triggers:
- I have a LED in /sys/class/leds
- I set "netdev" trigger on this LED
- I set ethernet interface for which the LED should blink
- if the HW can blink the LED for that particular ethernet interface, the driver should use HW blinking...
Quoted 6 lines
Quoted 2 lines
This should probably also work for HDD activity, but this would need a blockdev trigger first...
HDD activity is also HW/firmware monitored. The only thing the Kernel needs to to is to select what LED will be set as the HDD activity indicator.
Ditto as above, if we had a "blockdev" LED trigger.
Marek
Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
Em Wed, 19 May 2021 14:11:02 +0200
Marek Behún <[email protected]> escreveu:
Quoted 21 lines
Quoted 18 lines · On Wed, 19 May 2021 12:18:12 +0200 Mauro Carvalho Chehab…
Em Wed, 19 May 2021 10:02:53 +0200
Marek Behún <[email protected]> escreveu:Quoted 4 lines
What possible configurations does this support?
Does this blink on rx/tx activity for a specific ethernet port?
When the indicator is set to monitor Ethernet, it can work on either LAN1, LAN2 or both LAN interfaces.
Quoted 6 lines
There is a work in progress to add support for transparent offloading of LED triggers, with the netdev trigger being the first target.
This means that after that is done, you could implement this driver so that when netdev trigger is enabled on a supported interface, your driver will offload the blinking to the HW.
Quoted 7 lines
Quoted 4 lines
On NUC leds, this is already offloaded to HW/firmware.
All it takes is to tell the BIOS via ACPI/WMI what LED will be used for monitoring the Ethernet activity, and on what port(s).
Can the LED be put into software controlled mode and also into HW controlled mode so that HW blinks the LED on ethernet activity?
On a given time, a LED will either be in hardware-controlled mode or in Software-controlled one. It should be noticed that software-controlled mode should first be enabled by a BIOS option.
The default is to be hardware-controlled.
I don't intend to implement myself support for software-controlled mode in Kernel, as this will probably be a lot worse than letting the hardware control the led directly.
See, changing the LED in software on NUC happens via ACPI calls, meaning that the Kernel should be interrupted in order to run some code inside the BIOS that will actually program the hardware. Switching to BIOS produces context switching and may also interrupt the other CPUs, as the BIOS may need to do CPU locking, in order to prevent L1/L2/L3 cache issues.
On other words, if no extra care is taken, it could have bad side effects at the machine's performance and affect system's latency, eventually resulting on things like audio clicks and pops, if some audio is playing while such calls keep happening.
So, IMO, there's very little sense on trying to re-implement the already existing hardware-controlled events via software emulation.
Quoted 7 lines
If so, then this is what I am talking about: transparent HW offloading of LED triggers:
- I have a LED in/sys/class/leds
- I set "netdev" trigger on this LED
- I set ethernet interface for which the LED should blink
- if the HW can blink the LED for that particular ethernet interface, the driver should use HW blinking...
Sorry, but I guess I missed something here. Are you meaning to use the code under "ledtrig-netdev.c" or something else?
The code at ledtrig-netdev.c allocates a trigger data, initializes a spin lock, initializes a delayed work, registers a notifier, sets a trigger interval, etc. It is perfectly fine for software-controlled LEDs, but none of those will ever be used by the NUC driver, if it only implements HW blinking for the Ethernet interfaces (and, as said before, there's little sense emulating it via software on such devices).
Thanks,
Mauro
Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
Quoted 4 lines · On Wed, 19 May 2021 16:24:13 +0200 Mauro Carvalho Chehab…
On other words, if no extra care is taken, it could have bad side effects at the machine's performance and affect system's latency, eventually resulting on things like audio clicks and pops, if some audio is playing while such calls keep happening.
In general we want for every LED that is registered into kernel as a LED classdev to be possible to control the brightness by software. If the hardware supports it, it should be available. There is a _blocking .brightness_set_blocking callback for LEDs which may block when setting brightness.
But even if we did not want to support software control, the transparent trigger offloading is still relevant. See below.
Quoted 2 lines
So, IMO, there's very little sense on trying to re-implement the already existing hardware-controlled events via software emulation.
We have a misunderstanding here, probably because of my bad explanation, I will try to clarify.
Quoted 10 lines
Sorry, but I guess I missed something here. Are you meaning to use the code under "
ledtrig-netdev.c" or something else?The code at
ledtrig-netdev.callocates a trigger data, initializes a spin lock, initializes a delayed work, registers a notifier, sets a trigger interval, etc. It is perfectly fine for software-controlled LEDs, but none of those will ever be used by the NUC driver, if it only implements HW blinking for the Ethernet interfaces (and, as said before, there's little sense emulating it via software on such devices).
The idea of transparent offloading of LED triggers to HW (if HW supports it) is to have a consistent and unified interface.
Currently we have a driver (leds-ns2 I think) which allows putting the LED into HW controlled mode (to blink on SATA disk activity). This is done by writing 1 into /sys/class/leds/<LED>/sata.
In your proposal you are creating several sysfs files:
indicator
hdd_default (notice difference from "sata" sysfs file in leds-ns2
driver)
ethernet_type
So the problem here is that this API is not unified. This is different from how leds-ns2 driver does this, and both of these solutions are wrong, because they are not extendable.
The correct way to do this is via LED triggers, i.e. if I want a LED to blink on network activity, then I should use netdev trigger and nothing else. The netdev trigger should determine whether the underlying LED driver can set the LED to blink on network activity in HW. If HW supports it, netdev trigger should use this, otherwise netdev trigger should blink the LED in software.
Currently the netdev trigger does the blinking in software only (code in "ledtrig-netdev.c" file). There is a WIP to add the necessary support for the netdev trigger to have the ability to offload blinking to HW. I will try to respin this WIP and send patches for review.
Once netdev trigger supports this feature, you can implement your driver in this way. You can even make your driver depend on netdev trigger and set the specific LED into netdev triggering by default, and even forbidding anything else. But this is the corrent way to do this, instead of creating new sysfs API that is non-extendable.
I am sorry that I did not explain this thoroughly in previous mails. Hopefully this explanation is better.
Marek
PS: This is relevant for disk activity as well.
Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
Em Wed, 19 May 2021 17:55:03 +0200
Marek Behún <[email protected]> escreveu:
Quoted 11 lines
Quoted 4 lines · On Wed, 19 May 2021 16:24:13 +0200 Mauro Carvalho Chehab…
On other words, if no extra care is taken, it could have bad side effects at the machine's performance and affect system's latency, eventually resulting on things like audio clicks and pops, if some audio is playing while such calls keep happening.
In general we want for every LED that is registered into kernel as a LED classdev to be possible to control the brightness by software. If the hardware supports it, it should be available.
This is supported, but maybe not the same way as on other drivers.
There are two separate things: ON/OFF and LED brightness, when turned ON.
On other words, NUC leds allow to set the brightness ranging from 0 to 100, but if the brightness is, let's say 50%, it means that, when the LED is triggered by the hardware:
- ON would mean 50%; and
- OFF would mean 0%.
On other words, it actually adjusts the maximum brightness level.
Btw, this also applies to software control, as the hardware can still blink the LED, the available properties for software control indicator are:
- brightness.
- blink behavior and frequency;
- led color (available only if BIOS says that it is a multi-colored led);
Quoted 23 lines
There is a
_blocking
.brightness_set_blocking callback for LEDs which may block when setting brightness.
But even if we did not want to support software control, the transparent trigger offloading is still relevant. See below.Quoted 2 lines
So, IMO, there's very little sense on trying to re-implement the already existing hardware-controlled events via software emulation.
We have a misunderstanding here, probably because of my bad explanation, I will try to clarify.
Quoted 10 lines
Sorry, but I guess I missed something here. Are you meaning to use the code under "
ledtrig-netdev.c" or something else?The code at
ledtrig-netdev.callocates a trigger data, initializes a spin lock, initializes a delayed work, registers a notifier, sets a trigger interval, etc. It is perfectly fine for software-controlled LEDs, but none of those will ever be used by the NUC driver, if it only implements HW blinking for the Ethernet interfaces (and, as said before, there's little sense emulating it via software on such devices).
Quoted 2 lines
The idea of transparent offloading of LED triggers to HW (if HW supports it) is to have a consistent and unified interface.
Makes sense, but not sure if the current API will work.
Quoted 10 lines
Currently we have a driver (leds-ns2 I think) which allows putting the LED into HW controlled mode (to blink on SATA disk activity). This is done by writing 1 into /sys/class/leds/<LED>/sata.
In your proposal you are creating several sysfs files:
indicator
hdd_default(notice difference from "sata" sysfs file in leds-ns2
driver)
ethernet_type
Quoted 3 lines
So the problem here is that this API is not unified. This is different from how leds-ns2 driver does this, and both of these solutions are wrong, because they are not extendable.
Partially agreed, but I'm not so sure if the reverse is not true ;-)
I mean, the current LED API was designed and tested on drivers that allow direct control of the LED (and then extended to some cases where the hardware allows offloading).
The NUC API is just the opposite: there, the BIOS has full control of the hardware, but it provides an interface that allows changing the LED behavior, up to some extend. It also allows controlling the LED hardware and make it blink while it is suspended/hibernating, which is something that a direct LED control wouldn't allow.
So, for instance, if we stick with the current LED API, there's no way to tell that the power LED should:
- blink on every 5 seconds, using up to 20% of brightness when the system is suspended;
- strobe on every 10 seconds using up to 50% of brightness when the system is hibernated;
- use 100% of brigntness and don't blink when powered up.
Quoted 6 lines
The correct way to do this is via LED triggers, i.e. if I want a LED to blink on network activity, then I should use netdev trigger and nothing else. The netdev trigger should determine whether the underlying LED driver can set the LED to blink on network activity in HW. If HW supports it, netdev trigger should use this, otherwise netdev trigger should blink the LED in software.
I understand the desire of exposing the same API, but the current trigger code doesn't seem to be fit. I mean, the init sequence done at netdev_trig_activate():
trigger_data = kzalloc(sizeof(struct led_netdev_data), GFP_KERNEL); if (!trigger_data) return -ENOMEM; spin_lock_init(&trigger_data->lock); trigger_data->notifier.notifier_call = netdev_trig_notify; trigger_data->notifier.priority = 10; INIT_DELAYED_WORK(&trigger_data->work, netdev_trig_work); trigger_data->led_cdev = led_cdev; trigger_data->net_dev = NULL; trigger_data->device_name[0] = 0; trigger_data->mode = 0; atomic_set(&trigger_data->interval, msecs_to_jiffies(50)); trigger_data->last_activity = 0; led_set_trigger_data(led_cdev, trigger_data); rc = register_netdevice_notifier(&trigger_data->notifier); if (rc) kfree(trigger_data);
doesn't make sense when the LED will be trigged by the hardware, and registering a notifier for netdevice is overkill.
The exported attributes:
static struct attribute *netdev_trig_attrs[] = {
&dev_attr_device_name.attr,
&dev_attr_link.attr,
&dev_attr_rx.attr,
&dev_attr_tx.attr,
&dev_attr_interval.attr,
NULL
};
ATTRIBUTE_GROUPS(netdev_trig);also won't apply, as the NUC API doesn't support setting device_name, RX, TX, link or interval.
Instead, it allows to set:
- the maximum brightness;
- the color (if the LED is multi-colored);
- the physical port(s) that will be monitored:
- LAN1
- LAN2
- LAN1+LAN2
where LAN1 and LAN2 are two physical ports behind the NUC device. The netdev layer knows those as "eno1" and "enp5s0" (not necessarily at the same order).
Also, while netdev trigger seems to use just one device name, the NUC allows to monitor both interfaces at the same time.
See, unfortunately I can't see a common API that would fit nicely on both cases.
Quoted 11 lines
Currently the netdev trigger does the blinking in software only (code in "
ledtrig-netdev.c" file). There is a WIP to add the necessary support for the netdev trigger to have the ability to offload blinking to HW. I will try to respin this WIP and send patches for review.Once netdev trigger supports this feature, you can implement your driver in this way. You can even make your driver depend on netdev trigger
and set the specific LED into netdev triggering by default, and even forbidding anything else.
This is also probably one of the differences from other hardware:
In principle, *any* led can monitor *any* hardware event[1].
[1] There are some bitmaps at the interface that would allow the BIOS to restrict it, but, at least on the device I have (Hades Canyon), there's no such restriction: the same bitmap masks are returned for all LEDs.
Quoted 5 lines
But this is the corrent way to do this,
instead of creating new sysfs API that is non-extendable.I am sorry that I did not explain this thoroughly in previous mails. Hopefully this explanation is better.
Yes, it is a lot better. Thanks for the explanation!
Still, as I pointed above, I'm so far unable to see much in common with the way the existing LED drivers work and the way NUC LEDS are controlled.
So, as much I would love to just reuse something that already exists, perhaps it would make more sense to create a separate class for such kind of usage.
Quoted 4 lines
Marek
PS: This is relevant for disk activity as well.
Thanks,
Mauro
Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
Quoted 20 lines · On Wed, 19 May 2021 20:30:14 +0200 Mauro Carvalho Chehab…
Em Wed, 19 May 2021 17:55:03 +0200
Marek Behún <[email protected]> escreveu:Quoted 11 lines
Quoted 4 lines · On Wed, 19 May 2021 16:24:13 +0200 Mauro Carvalho Chehab…
On other words, if no extra care is taken, it could have bad side effects at the machine's performance and affect system's latency, eventually resulting on things like audio clicks and pops, if some audio is playing while such calls keep happening.
In general we want for every LED that is registered into kernel as a LED classdev to be possible to control the brightness by software. If the hardware supports it, it should be available.
This is supported, but maybe not the same way as on other drivers.
There are two separate things: ON/OFF and LED brightness, when turned ON.
Quoted 6 lines
On other words, NUC leds allow to set the brightness ranging from 0 to 100, but if the brightness is, let's say 50%, it means that, when the LED is triggered by the hardware:
- ON would mean 50%; and
- OFF would mean 0%.
Not a problem, there are other controller which also work this way, leds-turris-omnia for example. Also LED triggers are supposed to work this way: if a LED supports non-binary brightness (for exmaple 0-100), and the user sets brightness 50, and then a trigger, then the trigger should blink the LED with brightness 50.
Quoted 9 lines
On other words, it actually adjusts the maximum brightness level.
Btw, this also applies to software control, as the hardware can still blink the LED, the available properties for software control indicator are:
- brightness.
- blink behavior and frequency;
- led color (available only if BIOS says that it is a multi-colored led);
- if the hw supports setting the LED to blink with a specific frequency (not depending on any other HW like disk or ethernet, just blinking), you should also implement the .blink_set method (look at Documentation/leds/leds-class.rst section Hardware accelerated blink of LEDs)
- if BIOS says the LED is multi-colored, you should register it as multi-colored LED via multicolor framework
Quoted 49 lines
Quoted 24 lines
There is a
_blocking
.brightness_set_blocking callback for LEDs which may block when setting brightness.
But even if we did not want to support software control, the transparent trigger offloading is still relevant. See below.Quoted 3 lines
So, IMO, there's very little sense on trying to re-implement the already existing hardware-controlled events via software emulation.
We have a misunderstanding here, probably because of my bad explanation, I will try to clarify.
Quoted 10 lines
Sorry, but I guess I missed something here. Are you meaning to use the code under "
ledtrig-netdev.c" or something else?The code at
ledtrig-netdev.callocates a trigger data, initializes a spin lock, initializes a delayed work, registers a notifier, sets a trigger interval, etc. It is perfectly fine for software-controlled LEDs, but none of those will ever be used by the NUC driver, if it only implements HW blinking for the Ethernet interfaces (and, as said before, there's little sense emulating it via software on such devices).Quoted 2 lines
The idea of transparent offloading of LED triggers to HW (if HW supports it) is to have a consistent and unified interface.
Makes sense, but not sure if the current API will work.
Quoted 10 lines
Currently we have a driver (leds-ns2 I think) which allows putting the LED into HW controlled mode (to blink on SATA disk activity). This is done by writing 1 into /sys/class/leds/<LED>/sata.
In your proposal you are creating several sysfs files:
indicator
hdd_default(notice difference from "sata" sysfs file in leds-ns2
driver)
ethernet_typeQuoted 3 lines
So the problem here is that this API is not unified. This is different from how leds-ns2 driver does this, and both of these solutions are wrong, because they are not extendable.
Partially agreed, but I'm not so sure if the reverse is not true ;-)
I mean, the current LED API was designed and tested on drivers that allow direct control of the LED (and then extended to some cases where the hardware allows offloading).
Quoted 5 lines
The NUC API is just the opposite: there, the BIOS has full control of the hardware, but it provides an interface that allows changing the LED behavior, up to some extend. It also allows controlling the LED hardware and make it blink while it is suspended/hibernating, which is something that a direct LED control wouldn't allow.
If the controller can make the LED to do a specific behaviour when the system is suspended, please use the private LED trigger API and register a LED private trigger (a trigger that is only available for that specific LED) with a name like "suspend-indicator" or something.
Quoted 5 lines
So, for instance, if we stick with the current LED API, there's no way to tell that the power LED should:
- blink on every 5 seconds, using up to 20% of brightness when the system is suspended;
There is. Implement the .blink_set method.
Quoted 2 lines
- strobe on every 10 seconds using up to 50% of brightness when the system is hibernated;
Implement a LED private trigger.
Quoted 1 line
- use 100% of brigntness and don't blink when powered up.
Implement a LED private trigger.
Quoted 62 lines
Quoted 6 lines
The correct way to do this is via LED triggers, i.e. if I want a LED to blink on network activity, then I should use netdev trigger and nothing else. The netdev trigger should determine whether the underlying LED driver can set the LED to blink on network activity in HW. If HW supports it, netdev trigger should use this, otherwise netdev trigger should blink the LED in software.
I understand the desire of exposing the same API, but the current trigger code doesn't seem to be fit. I mean, the init sequence done at
netdev_trig_activate():
trigger_data= kzalloc(sizeof(structled_netdev_data),GFP_KERNEL); if (!trigger_data)return -ENOMEM; spin_lock_init(&trigger_data->lock); trigger_data->notifier.notifier_call = netdev_trig_notify; trigger_data->notifier.priority = 10; INIT_DELAYED_WORK(&trigger_data->work, netdev_trig_work); trigger_data->led_cdev = led_cdev; trigger_data->net_dev = NULL; trigger_data->device_name[0] = 0; trigger_data->mode = 0; atomic_set(&trigger_data->interval, msecs_to_jiffies(50)); trigger_data->last_activity = 0; led_set_trigger_data(led_cdev, trigger_data); rc = register_netdevice_notifier(&trigger_data->notifier); if (rc) kfree(trigger_data);doesn't make sense when the LED will be trigged by the hardware, and registering a notifier for netdevice is overkill.
The exported attributes:
static struct attribute *netdev_trig_attrs[] = { &dev_attr_device_name.attr, &dev_attr_link.attr, &dev_attr_rx.attr, &dev_attr_tx.attr, &dev_attr_interval.attr, NULL }; ATTRIBUTE_GROUPS(netdev_trig);also won't apply, as the NUC API doesn't support setting
device_name, RX, TX, link or interval.Instead, it allows to set:
- the maximum brightness;
- the color (if the LED is multi-colored);
- the physical port(s) that will be monitored:
- LAN1
- LAN2
- LAN1+LAN2
Quoted 3 lines
where LAN1 and LAN2 are two physical ports behind the NUC device. The netdev layer knows those as "eno1" and "enp5s0" (not necessarily at the same order).
The only problem I see with trigger_data in this case is that only one netdevice can be set, while your LED can be configured to blink on activity on two netdevices.
Otherwise all these settings are relevant.
What your driver offload callback should do (once HW offloading of LED triggers is merged) is this:
- the offload method is called by the netdev trigger
- the offload method looks at the trigger_data structure. This contains settings rx, tx, link, interval, device_name/device. If the device_name is "eno1" or "end5s0" (or better, if the device points to one of the 2 netdevices that are supported by the HW), and if the rx, tx, link and interval parameters are configured to values that can be done by the LED controller, than put the LED into HW controlled state (to blink with these parameters on network activity on that netdevice) and return 0
- otherwise the offload method should return -EOPNOTSUPP, and the netdev trigger will know that the requested settings are not supported by the HW, and the netdev trigger will blink the LED in SW
Quoted 2 lines
Also, while netdev trigger seems to use just one device name, the NUC allows to monitor both interfaces at the same time.
Yes. This can be solved in the future by extending netdev trigger to support blinking on activity on multiple netdevices. I also thought about this for use with another HW (mv88e6xxx switch).
Quoted 2 lines
See, unfortunately I can't see a common API that would fit nicely on both cases.
Well I can.
The only problem here is that it is not supported yet. I will try to send patches ASAP and poke Pavel to review them.
Quoted 29 lines
Quoted 6 lines
Currently the netdev trigger does the blinking in software only (code in "
ledtrig-netdev.c" file). There is a WIP to add the necessary support for the netdev trigger to have the ability to offload blinking to HW. I will try to respin this WIP and send patches for review.Quoted 6 lines
Once netdev trigger supports this feature, you can implement your driver in this way. You can even make your driver depend on netdev trigger
and set the specific LED into netdev triggering by default, and even forbidding anything else.
This is also probably one of the differences from other hardware:
In principle, *any* led can monitor *any* hardware event[1].[1] There are some bitmaps at the interface that would allow the BIOS to restrict it, but, at least on the device I have (Hades Canyon), there's no such restriction: the same bitmap masks are returned for all LEDs.
Quoted 5 lines
But this is the corrent way to do this,
instead of creating new sysfs API that is non-extendable.I am sorry that I did not explain this thoroughly in previous mails. Hopefully this explanation is better.
Yes, it is a lot better. Thanks for the explanation!
Quoted 7 lines
Still, as I pointed above, I'm so far unable to see much in common with the way the existing LED drivers work and the way NUC LEDS are controlled.
So, as much I would love to just reuse something that already exists, perhaps it would make more sense to create a separate class for such kind of usage.
The problem is that this API is not yet available in upstream kernel. I will try to push it and return to you.
In the meantime let's see what others think about your code.
Marek
Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
Em Thu, 20 May 2021 13:00:14 +0200
Marek Behún <[email protected]> escreveu:
Quoted 27 lines
Quoted 24 lines · On Wed, 19 May 2021 20:30:14 +0200 Mauro Carvalho Chehab…
Em Wed, 19 May 2021 17:55:03 +0200
Marek Behún <[email protected]> escreveu:Quoted 11 lines
Quoted 4 lines · On Wed, 19 May 2021 16:24:13 +0200 Mauro Carvalho Chehab…
On other words, if no extra care is taken, it could have bad side effects at the machine's performance and affect system's latency, eventually resulting on things like audio clicks and pops, if some audio is playing while such calls keep happening.
In general we want for every LED that is registered into kernel as a LED classdev to be possible to control the brightness by software. If the hardware supports it, it should be available.
This is supported, but maybe not the same way as on other drivers.
There are two separate things: ON/OFF and LED brightness, when turned ON.
On other words, NUC leds allow to set the brightness ranging from 0 to 100, but if the brightness is, let's say 50%, it means that, when the LED is triggered by the hardware:
Quoted 5 lines
Quoted 2 lines
- ON would mean 50%; and
- OFF would mean 0%.Not a problem, there are other controller which also work this way, leds-turris-omnia for example.
OK.
Quoted 15 lines
Also LED triggers are supposed to work
this way: if a LED supports non-binary brightness (for exmaple 0-100), and the user sets brightness 50, and then a trigger, then the trigger should blink the LED with brightness 50.Quoted 9 lines
On other words, it actually adjusts the maximum brightness level.
Btw, this also applies to software control, as the hardware can still blink the LED, the available properties for software control indicator are:
- brightness.
- blink behavior and frequency;
- led color (available only if BIOS says that it is a multi-colored led);
Quoted 5 lines
- if the hw supports setting the LED to blink with a specific frequency (not depending on any other HW like disk or ethernet, just blinking), you should also implement the .blink_set method (look at
Documentation/leds/leds-class.rstsection Hardware accelerated blink of LEDs)
Ok.
How the blink pattern is specified? NUC supports 3 different patterns (breathing, pulsing, strobing).
Quoted 2 lines
- if BIOS says the LED is multi-colored, you should register it as multi-colored LED via multicolor framework
Ok. I'll check how much work this will lead after we finish the API discussions, as all sysfs attributes that won't fit at the triggers will need to be implemented twice - one for mono-colored and another one for multicolored, as the priv pointer will use different structures.
Quoted 31 lines
Quoted 23 lines
The exported attributes:
static struct attribute *netdev_trig_attrs[] = { &dev_attr_device_name.attr, &dev_attr_link.attr, &dev_attr_rx.attr, &dev_attr_tx.attr, &dev_attr_interval.attr, NULL }; ATTRIBUTE_GROUPS(netdev_trig);also won't apply, as the NUC API doesn't support setting
device_name, RX, TX, link or interval.Instead, it allows to set:
- the maximum brightness;
- the color (if the LED is multi-colored);
- the physical port(s) that will be monitored:
- LAN1
- LAN2
- LAN1+LAN2Quoted 3 lines
where LAN1 and LAN2 are two physical ports behind the NUC device. The netdev layer knows those as "eno1" and "enp5s0" (not necessarily at the same order).
The only problem I see with
trigger_datain this case is that only one netdevice can be set, while your LED can be configured to blink on activity on two netdevices.
Quoted 16 lines
Otherwise all these settings are relevant.
What your driver offload callback should do (once HW offloading of LED triggers is merged) is this:
- the offload method is called by the netdev trigger
- the offload method looks at thetrigger_datastructure. This contains settings rx, tx, link, interval, device_name/device. If thedevice_nameis "eno1" or "end5s0" (or better, if the device points to one of the 2 netdevices that are supported by the HW), and if the rx, tx, link and interval parameters are configured to values that can be done by the LED controller, than put the LED into HW controlled state (to blink with these parameters on network activity on that netdevice) and return 0
- otherwise the offload method should return-EOPNOTSUPP, and the netdev trigger will know that the requested settings are not supported by the HW, and the netdev trigger will blink the LED in SW
See, there's nothing that the driver can possible do with rx, tx, link, interval, device_name/device, as the the BIOS allows to set to "LAN1", "LAN2" or "LAN1+LAN2". the WMI interface doesn't provide *any* information about what LAN1 means.
In the case of my NUC, there are even two different drivers:
00:1f.6 Ethernet controller: Intel Corporation Ethernet Connection (2) I219-LM (rev 31) Kernel modules: e1000e
05:00.0 Ethernet controller: Intel Corporation I210 Gigabit Network Connection (rev 03) Kernel modules: igb
So, even the probing order can be different after a reboot.
So, there isn't a portable way to tell if LAN1 means to either "eno1" or "end5s0"(*), as netdev and the associated net drivers talk with the hardware directly, and not via BIOS. So, the BIOS internal name is not known. Even the DMI tables don't tell it:
Handle 0x000F, DMI type 8, 9 bytes Port Connector Information
Internal Reference Designator: CON4501 Internal Connector Type: None External Reference Designator: LAN External Connector Type: RJ-45 Port Type: Network Port
Handle 0x0010, DMI type 8, 9 bytes Port Connector Information
Internal Reference Designator: CON4401 Internal Connector Type: None External Reference Designator: LAN External Connector Type: RJ-45 Port Type: Network Port
(*) I'm using the interface names on this specific model, but
I'm pretty sure other models will have different names and will even use different network drivers.
The point is, while the current netdev trigger API can be generic enough when the LED is controlled by netdev, it is *not* generic enough to cover the case where it is trigged by the firmware, as the information exposed by the firmware can be (and it is on this case) completely different than what netdev exposes.
Quoted 11 lines
Quoted 2 lines
Also, while netdev trigger seems to use just one device name, the NUC allows to monitor both interfaces at the same time.
Yes. This can be solved in the future by extending netdev trigger to support blinking on activity on multiple netdevices. I also thought about this for use with another HW (mv88e6xxx switch).
Quoted 2 lines
See, unfortunately I can't see a common API that would fit nicely on both cases.
Well I can.
Then the API needs to change, in order to allow to abstract from netdev-centric view of Ethernet interfaces. Or, instead, some other trigger is needed for firmware-controlled events.
-
One thing that it is not clear to me: let's say that the LED called "front1" is currently handling Ethernet events, but the user wants to use, instead, the "front2" LED, disabling the "front1" one (or using for another event, like wifi, which is not monitored on BIOS default).
How this can be done using the trigger's API?
Quoted 2 lines
The only problem here is that it is not supported yet. I will try to send patches ASAP and poke Pavel to review them.
Ok. Please c/c on such patches.
Quoted 33 lines
Quoted 33 lines
Quoted 6 lines
Currently the netdev trigger does the blinking in software only (code in "
ledtrig-netdev.c" file). There is a WIP to add the necessary support for the netdev trigger to have the ability to offload blinking to HW. I will try to respin this WIP and send patches for review.Quoted 6 lines
Once netdev trigger supports this feature, you can implement your driver in this way. You can even make your driver depend on netdev trigger
and set the specific LED into netdev triggering by default, and even forbidding anything else.
This is also probably one of the differences from other hardware:
In principle, *any* led can monitor *any* hardware event[1].[1] There are some bitmaps at the interface that would allow the BIOS to restrict it, but, at least on the device I have (Hades Canyon), there's no such restriction: the same bitmap masks are returned for all LEDs.
Quoted 5 lines
But this is the corrent way to do this,
instead of creating new sysfs API that is non-extendable.I am sorry that I did not explain this thoroughly in previous mails. Hopefully this explanation is better.
Yes, it is a lot better. Thanks for the explanation!
Still, as I pointed above, I'm so far unable to see much in common with the way the existing LED drivers work and the way NUC LEDS are controlled.
Quoted 6 lines
Quoted 3 lines
So, as much I would love to just reuse something that already exists, perhaps it would make more sense to create a separate class for such kind of usage.
The problem is that this API is not yet available in upstream kernel. I will try to push it and return to you.
Ok.
Quoted 2 lines
In the meantime let's see what others think about your code.
Ok, thank you!
Thanks,
Mauro
Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
Quoted 120 lines · On Thu, 20 May 2021 18:00:28 +0200 Mauro Carvalho Chehab…
Em Thu, 20 May 2021 13:00:14 +0200
Marek Behún <[email protected]> escreveu:Quoted 27 lines
Quoted 24 lines · On Wed, 19 May 2021 20:30:14 +0200 Mauro Carvalho Chehab…
Em Wed, 19 May 2021 17:55:03 +0200
Marek Behún <[email protected]> escreveu:Quoted 11 lines
Quoted 4 lines · On Wed, 19 May 2021 16:24:13 +0200 Mauro Carvalho Chehab…
On other words, if no extra care is taken, it could have bad side effects at the machine's performance and affect system's latency, eventually resulting on things like audio clicks and pops, if some audio is playing while such calls keep happening.
In general we want for every LED that is registered into kernel as a LED classdev to be possible to control the brightness by software. If the hardware supports it, it should be available.
This is supported, but maybe not the same way as on other drivers.
There are two separate things: ON/OFF and LED brightness, when turned ON.
On other words, NUC leds allow to set the brightness ranging from 0 to 100, but if the brightness is, let's say 50%, it means that, when the LED is triggered by the hardware:
Quoted 5 lines
Quoted 2 lines
- ON would mean 50%; and
- OFF would mean 0%.Not a problem, there are other controller which also work this way, leds-turris-omnia for example.
OK.
Quoted 15 lines
Also LED triggers are supposed to work
this way: if a LED supports non-binary brightness (for exmaple 0-100), and the user sets brightness 50, and then a trigger, then the trigger should blink the LED with brightness 50.Quoted 9 lines
On other words, it actually adjusts the maximum brightness level.
Btw, this also applies to software control, as the hardware can still blink the LED, the available properties for software control indicator are:
- brightness.
- blink behavior and frequency;
- led color (available only if BIOS says that it is a multi-colored led);Quoted 5 lines
- if the hw supports setting the LED to blink with a specific frequency (not depending on any other HW like disk or ethernet, just blinking), you should also implement the .blink_set method (look at
Documentation/leds/leds-class.rstsection Hardware accelerated blink of LEDs)Ok.
How the blink pattern is specified? NUC supports 3 different patterns (breathing, pulsing, strobing).
Quoted 2 lines
- if BIOS says the LED is multi-colored, you should register it as multi-colored LED via multicolor framework
Ok. I'll check how much work this will lead after we finish the API discussions, as all sysfs attributes that won't fit at the triggers will need to be implemented twice - one for mono-colored and another one for multicolored, as the priv pointer will use different structures.
Quoted 31 lines
Quoted 23 lines
The exported attributes:
static struct attribute *netdev_trig_attrs[] = { &dev_attr_device_name.attr, &dev_attr_link.attr, &dev_attr_rx.attr, &dev_attr_tx.attr, &dev_attr_interval.attr, NULL }; ATTRIBUTE_GROUPS(netdev_trig);also won't apply, as the NUC API doesn't support setting
device_name, RX, TX, link or interval.Instead, it allows to set:
- the maximum brightness;
- the color (if the LED is multi-colored);
- the physical port(s) that will be monitored:
- LAN1
- LAN2
- LAN1+LAN2Quoted 3 lines
where LAN1 and LAN2 are two physical ports behind the NUC device. The netdev layer knows those as "eno1" and "enp5s0" (not necessarily at the same order).
The only problem I see with
trigger_datain this case is that only one netdevice can be set, while your LED can be configured to blink on activity on two netdevices.Quoted 16 lines
Otherwise all these settings are relevant.
What your driver offload callback should do (once HW offloading of LED triggers is merged) is this:
- the offload method is called by the netdev trigger
- the offload method looks at thetrigger_datastructure. This contains settings rx, tx, link, interval, device_name/device. If thedevice_nameis "eno1" or "end5s0" (or better, if the device points to one of the 2 netdevices that are supported by the HW), and if the rx, tx, link and interval parameters are configured to values that can be done by the LED controller, than put the LED into HW controlled state (to blink with these parameters on network activity on that netdevice) and return 0
- otherwise the offload method should return-EOPNOTSUPP, and the netdev trigger will know that the requested settings are not supported by the HW, and the netdev trigger will blink the LED in SW
Quoted 4 lines
See, there's nothing that the driver can possible do with rx, tx, link, interval, device_name/device, as the the BIOS allows to set to "LAN1", "LAN2" or "LAN1+LAN2". the WMI interface doesn't provide *any* information about what LAN1 means.
On the contrary, there is something the driver can do with these attributes. If the specific combination is not supported, the driver should return -EOPNOTSUPP in the trigger_offload method and let the netdev trigger do the work in software. What exactly do the LEDs do when configured to blink on activity on a network device? Do they just blink on RX/TX, and otherwise are off? Or are they on when a cable is plugged, blink on rx/tx and otherwise off?
Quoted 32 lines
In the case of my NUC, there are even two different drivers:
00:1f.6 Ethernet controller: Intel Corporation Ethernet Connection (2) I219-LM (rev 31) Kernel modules: e1000e
05:00.0 Ethernet controller: Intel Corporation I210 Gigabit Network Connection (rev 03) Kernel modules: igb
So, even the probing order can be different after a reboot.
So, there isn't a portable way to tell if LAN1 means to either "eno1" or "end5s0"(*), as netdev and the associated net drivers talk with the hardware directly, and not via BIOS. So, the BIOS internal name is not known. Even the DMI tables don't tell it:
Handle
0x000F, DMI type 8, 9 bytes Port Connector Information
Internal Reference Designator: CON4501 Internal Connector Type: None External Reference Designator: LAN External Connector Type: RJ-45 Port Type: Network PortHandle
0x0010, DMI type 8, 9 bytes Port Connector Information
Internal Reference Designator: CON4401 Internal Connector Type: None External Reference Designator: LAN External Connector Type: RJ-45 Port Type: Network Port
Quoted 3 lines
(*) I'm using the interface names on this specific model, but
I'm pretty sure other models will have different names and will even use different network drivers.
Have you looked into DSDT and SSDT tables?
Quoted 6 lines
The point is, while the current netdev trigger API can be generic enough when the LED is controlled by netdev, it is *not* generic enough to cover the case where it is trigged by the firmware, as the information exposed by the firmware can be (and it is on this case) completely different than what netdev exposes.
If even DSDT data is not enough to reliably find out which of the 2 network interfaces belongs to which LED setting, the worst case scenario here is for your driver to need to have a list containing this information for specific motherboards, and other people can then extend the driver to support their motherboards as well.
Quoted 8 lines
Quoted 8 lines
Quoted 3 lines
Also, while netdev trigger seems to use just one device name, the NUC allows to monitor both interfaces at the same time.
Yes. This can be solved in the future by extending netdev trigger to support blinking on activity on multiple netdevices. I also thought about this for use with another HW (mv88e6xxx switch).
Quoted 8 lines
Quoted 4 lines
Quoted 2 lines
See, unfortunately I can't see a common API that would fit nicely on both cases.
Well I can.
Then the API needs to change, in order to allow to abstract from netdev-centric view of Ethernet interfaces. Or, instead, some other trigger is needed for firmware-controlled events.
No. If the necessary information for determining which network interface pairs to LED1 and which to LED2 cannot be reliably determined from ACPI tables, then IMO the driver should specify this information for each motherboard that wants to use this feature.
Quoted 9 lines
-
One thing that it is not clear to me: let's say that the LED called "front1" is currently handling Ethernet events, but the user wants to use, instead, the "front2" LED, disabling the "front1" one (or using for another event, like wifi, which is not monitored on BIOS default).
How this can be done using the trigger's API?
cd /sys/class/leds/front1
echo none >trigger
cd /sys/class/leds/front2
echo netdev >trigger
echo ifname >device_name
echo 1 >rx
echo 1 >tx
Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
Em Thu, 20 May 2021 18:36:33 +0200
Marek Behún <[email protected]> escreveu:
Quoted 128 lines
Quoted 120 lines · On Thu, 20 May 2021 18:00:28 +0200 Mauro Carvalho Chehab…
Em Thu, 20 May 2021 13:00:14 +0200
Marek Behún <[email protected]> escreveu:Quoted 27 lines
Quoted 24 lines · On Wed, 19 May 2021 20:30:14 +0200 Mauro Carvalho Chehab…
Em Wed, 19 May 2021 17:55:03 +0200
Marek Behún <[email protected]> escreveu:Quoted 11 lines
Quoted 4 lines · On Wed, 19 May 2021 16:24:13 +0200 Mauro Carvalho Chehab…
On other words, if no extra care is taken, it could have bad side effects at the machine's performance and affect system's latency, eventually resulting on things like audio clicks and pops, if some audio is playing while such calls keep happening.
In general we want for every LED that is registered into kernel as a LED classdev to be possible to control the brightness by software. If the hardware supports it, it should be available.
This is supported, but maybe not the same way as on other drivers.
There are two separate things: ON/OFF and LED brightness, when turned ON.
On other words, NUC leds allow to set the brightness ranging from 0 to 100, but if the brightness is, let's say 50%, it means that, when the LED is triggered by the hardware:
Quoted 5 lines
Quoted 2 lines
- ON would mean 50%; and
- OFF would mean 0%.Not a problem, there are other controller which also work this way, leds-turris-omnia for example.
OK.
Quoted 15 lines
Also LED triggers are supposed to work
this way: if a LED supports non-binary brightness (for exmaple 0-100), and the user sets brightness 50, and then a trigger, then the trigger should blink the LED with brightness 50.Quoted 9 lines
On other words, it actually adjusts the maximum brightness level.
Btw, this also applies to software control, as the hardware can still blink the LED, the available properties for software control indicator are:
- brightness.
- blink behavior and frequency;
- led color (available only if BIOS says that it is a multi-colored led);Quoted 5 lines
- if the hw supports setting the LED to blink with a specific frequency (not depending on any other HW like disk or ethernet, just blinking), you should also implement the .blink_set method (look at
Documentation/leds/leds-class.rstsection Hardware accelerated blink of LEDs)Ok.
How the blink pattern is specified? NUC supports 3 different patterns (breathing, pulsing, strobing).
Quoted 2 lines
- if BIOS says the LED is multi-colored, you should register it as multi-colored LED via multicolor framework
Ok. I'll check how much work this will lead after we finish the API discussions, as all sysfs attributes that won't fit at the triggers will need to be implemented twice - one for mono-colored and another one for multicolored, as the priv pointer will use different structures.
Quoted 31 lines
Quoted 23 lines
The exported attributes:
static struct attribute *netdev_trig_attrs[] = { &dev_attr_device_name.attr, &dev_attr_link.attr, &dev_attr_rx.attr, &dev_attr_tx.attr, &dev_attr_interval.attr, NULL }; ATTRIBUTE_GROUPS(netdev_trig);also won't apply, as the NUC API doesn't support setting
device_name, RX, TX, link or interval.Instead, it allows to set:
- the maximum brightness;
- the color (if the LED is multi-colored);
- the physical port(s) that will be monitored:
- LAN1
- LAN2
- LAN1+LAN2Quoted 3 lines
where LAN1 and LAN2 are two physical ports behind the NUC device. The netdev layer knows those as "eno1" and "enp5s0" (not necessarily at the same order).
The only problem I see with
trigger_datain this case is that only one netdevice can be set, while your LED can be configured to blink on activity on two netdevices.Quoted 16 lines
Otherwise all these settings are relevant.
What your driver offload callback should do (once HW offloading of LED triggers is merged) is this:
- the offload method is called by the netdev trigger
- the offload method looks at thetrigger_datastructure. This contains settings rx, tx, link, interval, device_name/device. If thedevice_nameis "eno1" or "end5s0" (or better, if the device points to one of the 2 netdevices that are supported by the HW), and if the rx, tx, link and interval parameters are configured to values that can be done by the LED controller, than put the LED into HW controlled state (to blink with these parameters on network activity on that netdevice) and return 0
- otherwise the offload method should return-EOPNOTSUPP, and the netdev trigger will know that the requested settings are not supported by the HW, and the netdev trigger will blink the LED in SWQuoted 4 lines
See, there's nothing that the driver can possible do with rx, tx, link, interval, device_name/device, as the the BIOS allows to set to "LAN1", "LAN2" or "LAN1+LAN2". the WMI interface doesn't provide *any* information about what LAN1 means.
Quoted 4 lines
On the contrary, there is something the driver can do with these attributes. If the specific combination is not supported, the driver should return
-EOPNOTSUPPin thetrigger_offloadmethod and let the netdev trigger do the work in software.
Letting netdev to trigger is something we don't want to allow, as this can cause side effects, making it doing slow the system due to BIOS calls for no good reason.
Quoted 4 lines
What exactly do the LEDs do
when configured to blink on activity on a network device? Do they just blink on RX/TX, and otherwise are off? Or are they on when a cable is plugged, blink on rx/tx and otherwise off?
They are on when a cable is plugged, blink on rx/tx and otherwise off.
Worth mentioning that, besides the LEDs controlled by this driver, each RJ-45 port also a couple leds that behave just like normal RJ-45 ones:
a yellow led for Ethernet PHY detection and a green one for traffic.
Quoted 33 lines
Quoted 33 lines
In the case of my NUC, there are even two different drivers:
00:1f.6 Ethernet controller: Intel Corporation Ethernet Connection (2) I219-LM (rev 31) Kernel modules: e1000e
05:00.0 Ethernet controller: Intel Corporation I210 Gigabit Network Connection (rev 03) Kernel modules: igb
So, even the probing order can be different after a reboot.
So, there isn't a portable way to tell if LAN1 means to either "eno1" or "end5s0"(*), as netdev and the associated net drivers talk with the hardware directly, and not via BIOS. So, the BIOS internal name is not known. Even the DMI tables don't tell it:
Handle
0x000F, DMI type 8, 9 bytes Port Connector Information
Internal Reference Designator: CON4501 Internal Connector Type: None External Reference Designator: LAN External Connector Type: RJ-45 Port Type: Network PortHandle
0x0010, DMI type 8, 9 bytes Port Connector Information
Internal Reference Designator: CON4401 Internal Connector Type: None External Reference Designator: LAN External Connector Type: RJ-45 Port Type: Network Port
Quoted 5 lines
Quoted 3 lines
(*) I'm using the interface names on this specific model, but
I'm pretty sure other models will have different names and will even use different network drivers.Have you looked into DSDT and SSDT tables?
It doesn't help.
# ./generate/unix/bin/acpidump -b # for i in *.dat; do # ./generate/unix/bin/iasl -d $i # done
$ grep -i lan *dsl dsdt.dsl: \_SB.PCI0.GLAN.GPEH () dsdt.dsl: Device (GLAN) dsdt.dsl: Notify (GLAN, 0x02) // Device Wake dsdt.dsl: _HID = "ELAN2097" dsdt.dsl: Name (_MLS, Package (0x01) // _MLS: Multiple Language String
Quoted 7 lines
Quoted 6 lines
The point is, while the current netdev trigger API can be generic enough when the LED is controlled by netdev, it is *not* generic enough to cover the case where it is trigged by the firmware, as the information exposed by the firmware can be (and it is on this case) completely different than what netdev exposes.
Quoted 5 lines
If even DSDT data is not enough to reliably find out which of the 2 network interfaces belongs to which LED setting, the worst case scenario here is for your driver to need to have a list containing this information for specific motherboards, and other people can then extend the driver to support their motherboards as well.
Needing something like that sucks and it is hard to maintain, and depends on people reporting issues.
Ok, on some cases, there are no other options, but this is not the case here, as the user of such API that wants to monitor just a single interface (default is to monitor both) can easily ask the driver to monitor LAN1. If it doesn't work, switch to LAN2.
That's a way more elegant than adding some guessing code that would be checking for the machine codes, eventually printing a warning and disabling support for monitoring LAN when the machine is not properly identified.
Also, implementing such table can be painful. I can't see an easy way to implement it, specially without having any information about how all other models that support the WMI API are shipped, and how to map "LAN1", "LAN2" into something that matches netdev detection. OK, if each one have a different BUS ID,
a mapping table could associate each one with a different BUS ID, and then some logic at netdev would convert BUS ID into the device name.
Quoted 12 lines
Quoted 12 lines
Quoted 11 lines
Quoted 2 lines
Also, while netdev trigger seems to use just one device name, the NUC allows to monitor both interfaces at the same time.
Yes. This can be solved in the future by extending netdev trigger to support blinking on activity on multiple netdevices. I also thought about this for use with another HW (mv88e6xxx switch).
Quoted 2 lines
See, unfortunately I can't see a common API that would fit nicely on both cases.
Well I can.
Quoted 8 lines
Quoted 3 lines
Then the API needs to change, in order to allow to abstract from netdev-centric view of Ethernet interfaces. Or, instead, some other trigger is needed for firmware-controlled events.
No. If the necessary information for determining which network interface pairs to LED1 and which to LED2 cannot be reliably determined from ACPI tables, then IMO the driver should specify this information for each motherboard that wants to use this feature.
What's the gain of adding such extra complexity to the driver?
All the user wants is to blink a led only for one of the LAN ports.
Denying it and using a more complex API doesn't make much sense, IMO.
Quoted 8 lines
Quoted 8 lines
-
One thing that it is not clear to me: let's say that the LED called "front1" is currently handling Ethernet events, but the user wants to use, instead, the "front2" LED, disabling the "front1" one (or using for another event, like wifi, which is not monitored on BIOS default).
Quoted 6 lines
Quoted 1 line
How this can be done using the trigger's API?
cd
/sys/class/leds/front1
echo none >trigger
cd/sys/class/leds/front2
echo netdev >trigger
Clear enough to me.
Quoted 3 lines
echo ifname >device_name
echo 1 >rx
echo 1 >tx
And that's the part that it makes no sense for this hardware ;-)
It can't identify RX/TX in separate. It can only monitor both RX and TX at the same time.
So, for this specific device, neither "rx", "tx" or "interval" attributes should be shown.
Provided that we could use the Ethernet label as "device_name", this would work:
echo "LAN1+LAN2" > device_name
echo "LAN1" > device_name
echo "LAN2" > device_name
or, in order to avoid confusion, we could use a different name on this specific driver, like:
echo "LAN1+LAN2" > port_name
echo "LAN1" > port_name
echo "LAN2" > port_name
FYI, the default (LAN1+LAN2) is usually good enough for most use cases, for a couple of reasons:
- I guess only high end models have more than one Eth port;
- Most people use just one LAN cable;
- the back panel leds at the RJ45 are enough to check if ethernet signal was detected.
Yet, at least for me, having the NUC led monitoring eth is interesting, as here I use the LAN ports to connect with some testing hardware.
So, when the LED is on, it means that my hardware is turned on and the Ethernet connection should be working.
Thanks,
Mauro
Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
Quoted 14 lines · On Thu, 20 May 2021 20:59:33 +0200 Mauro Carvalho Chehab…
Quoted 4 lines
On the contrary, there is something the driver can do with these attributes. If the specific combination is not supported, the driver should return
-EOPNOTSUPPin thetrigger_offloadmethod and let the netdev trigger do the work in software.Letting netdev to trigger is something we don't want to allow, as this can cause side effects, making it doing slow the system due to BIOS calls for no good reason.
Quoted 4 lines
What exactly do the LEDs do
when configured to blink on activity on a network device? Do they just blink on RX/TX, and otherwise are off? Or are they on when a cable is plugged, blink on rx/tx and otherwise off?
Quoted 5 lines
They are on when a cable is plugged, blink on rx/tx and otherwise off.
Worth mentioning that, besides the LEDs controlled by this driver, each RJ-45 port also a couple leds that behave just like normal RJ-45 ones:
a yellow led for Ethernet PHY detection and a green one for traffic.
So what the LED does when configured for ethernet is almost equivalent to netdev setting [link=1, rx=1, activity=1]. Almost because we still have the correct device setting and interval setting.
Theoretically what you can do is deny the netdev trigger for every other netdev setting (since, according to you, it would use too much CPU time in BIOS via software control). This could be done by the offload method returning another error value, or maybe just returning 0 and printing info about this into kernel log. I wonder what others think about this possible resolution.
Quoted 3 lines
Quoted 1 line
Have you looked into DSDT and SSDT tables?
It doesn't help.
Pity :(
Quoted 8 lines
Quoted 5 lines
If even DSDT data is not enough to reliably find out which of the 2 network interfaces belongs to which LED setting, the worst case scenario here is for your driver to need to have a list containing this information for specific motherboards, and other people can then extend the driver to support their motherboards as well.
Needing something like that sucks and it is hard to maintain, and depends on people reporting issues.
I don't see much difference between this and various drivers having different OF compatible strings for different chips all supported by one driver.
Quoted 35 lines
Ok, on some cases, there are no other options, but this is not the case here, as the user of such API that wants to monitor just a single interface (default is to monitor both) can easily ask the driver to monitor LAN1. If it doesn't work, switch to LAN2.
That's a way more elegant than adding some guessing code that would be checking for the machine codes, eventually printing a warning and disabling support for monitoring LAN when the machine is not properly identified.
Also, implementing such table can be painful. I can't see an easy way to implement it, specially without having any information about how all other models that support the WMI API are shipped, and how to map "LAN1", "LAN2" into something that matches netdev detection. OK, if each one have a different BUS ID,
a mapping table could associate each one with a different BUS ID, and then some logic at netdev would convert BUS ID into the device name.Quoted 16 lines
Quoted 15 lines
Quoted 11 lines
Quoted 2 lines
Also, while netdev trigger seems to use just one device name, the NUC allows to monitor both interfaces at the same time.
Yes. This can be solved in the future by extending netdev trigger to support blinking on activity on multiple netdevices. I also thought about this for use with another HW (mv88e6xxx switch).
Quoted 2 lines
See, unfortunately I can't see a common API that would fit nicely on both cases.
Well I can.
Then the API needs to change, in order to allow to abstract from netdev-centric view of Ethernet interfaces. Or, instead, some other trigger is needed for firmware-controlled events.
Quoted 6 lines
Quoted 4 lines
No. If the necessary information for determining which network interface pairs to LED1 and which to LED2 cannot be reliably determined from ACPI tables, then IMO the driver should specify this information for each motherboard that wants to use this feature.
What's the gain of adding such extra complexity to the driver?
Having a consistent API on different devices is a benefit in itself, I would think.
Quoted 3 lines
All the user wants is to blink a led only for one of the LAN ports.
Denying it and using a more complex API doesn't make much sense, IMO.
As I see it you are the one wanting to introduce more complexity into the sysfs ABI. There is already a solution together with documentation and everything for when the user wants to "blink a led only for one of the LAN ports". It is the netdev trigger. And you want to complicate that ABI.
Quoted 21 lines
Quoted 8 lines
Quoted 8 lines
-
One thing that it is not clear to me: let's say that the LED called "front1" is currently handling Ethernet events, but the user wants to use, instead, the "front2" LED, disabling the "front1" one (or using for another event, like wifi, which is not monitored on BIOS default).
Quoted 6 lines
Quoted 1 line
How this can be done using the trigger's API?
cd
/sys/class/leds/front1
echo none >trigger
cd/sys/class/leds/front2
echo netdev >triggerClear enough to me.
Quoted 3 lines
echo ifname >device_name
echo 1 >rx
echo 1 >tx
Quoted 7 lines
And that's the part that it makes no sense for this hardware ;-)
It can't identify RX/TX in separate. It can only monitor both RX and TX at the same time.
So, for this specific device, neither "rx", "tx" or "interval" attributes should be shown.
If a netdev setting is not supported by the HW, it should be done in SW. You say that for this controller it would be bad to do in SW, because it would take too much time in BIOS calls. (I wonder how much...) If this is really the case then I still think it is more preferable to do this via netdev trigger, and forbid settings not supported by HW. The result could be:
# I want the LED to blink on ethernet activity
$ echo netdev >trigger
# ok, but I only wan't it to blink on rx activity, not tx
$ echo 0 >tx
Operation not supported.Pavel, what is your opinion here?
Marek
Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
Em Thu, 20 May 2021 22:07:03 +0200
Marek Behún <[email protected]> escreveu:
Quoted 27 lines
Quoted 14 lines · On Thu, 20 May 2021 20:59:33 +0200 Mauro Carvalho Chehab…
Quoted 4 lines
On the contrary, there is something the driver can do with these attributes. If the specific combination is not supported, the driver should return
-EOPNOTSUPPin thetrigger_offloadmethod and let the netdev trigger do the work in software.Letting netdev to trigger is something we don't want to allow, as this can cause side effects, making it doing slow the system due to BIOS calls for no good reason.
Quoted 4 lines
What exactly do the LEDs do
when configured to blink on activity on a network device? Do they just blink on RX/TX, and otherwise are off? Or are they on when a cable is plugged, blink on rx/tx and otherwise off?Quoted 5 lines
They are on when a cable is plugged, blink on rx/tx and otherwise off.
Worth mentioning that, besides the LEDs controlled by this driver, each RJ-45 port also a couple leds that behave just like normal RJ-45 ones:
a yellow led for Ethernet PHY detection and a green one for traffic.So what the LED does when configured for ethernet is almost equivalent to netdev setting [link=1, rx=1, activity=1]. Almost because we still have the correct device setting and interval setting.
Quoted 6 lines
Theoretically what you can do is deny the netdev trigger for every other netdev setting (since, according to you, it would use too much CPU time in BIOS via software control). This could be done by the offload method returning another error value, or maybe just returning 0 and printing info about this into kernel log. I wonder what others think about this possible resolution.
IMO, it would be preferred to have a different trigger here, as this is not a netdev-based trigger. So, its implementation should not call:
register_netdevice_notifier()
nor set timers, etc.
See, the hardware won't use any information provided by the netdev, trigger and the API is not the same, as the hardware trigger only wants to know if just one interface will trigger the led or both.
Quoted 6 lines
Quoted 6 lines
Quoted 5 lines
If even DSDT data is not enough to reliably find out which of the 2 network interfaces belongs to which LED setting, the worst case scenario here is for your driver to need to have a list containing this information for specific motherboards, and other people can then extend the driver to support their motherboards as well.
Quoted 6 lines
Quoted 2 lines
Needing something like that sucks and it is hard to maintain, and depends on people reporting issues.
I don't see much difference between this and various drivers having different OF compatible strings for different chips all supported by one driver.
It is somewhat similar: on my experiences, the upstream OF compatibles are almost always outdated uptream: only OOT Kernels have the full OF stuff :-p
The major difference is that hardware vendors usually develop and provide OF.
In this case, you want users to fill bug reports that will ask a Kernel developer to add new entries for their machines to work properly. While we do this on other drivers, doing that is time consuming and may lead into errors. Believe me: this is needed on media drivers, as there are things like GPIOs that are device-specific. It is a pain to maintain those things.
Quoted 9 lines
Quoted 9 lines
Quoted 8 lines
Quoted 3 lines
Then the API needs to change, in order to allow to abstract from netdev-centric view of Ethernet interfaces. Or, instead, some other trigger is needed for firmware-controlled events.
No. If the necessary information for determining which network interface pairs to LED1 and which to LED2 cannot be reliably determined from ACPI tables, then IMO the driver should specify this information for each motherboard that wants to use this feature.
Quoted 4 lines
Quoted 1 line
What's the gain of adding such extra complexity to the driver?
Having a consistent API on different devices is a benefit in itself, I would think.
It wouldn't be consistent. Hardware sees the events on different ways than netdev, and associating netdev's view of the interface with the hardware's view will always be an issue, on any driver that would trigger such kind of events.
See, let's assume someone would implement a hardware trigger for the LEDs on an 48-ports Ethernet switch, and different versions of such hub would use different Ethernet drivers.
No matter how netdev sees the hardware, or if some of the ports can be replaced (some devices allow port hot-plugging), from userspace perspective, what it really matter is the port number as seen at the switch panel, no matter how netdev sees it.
So, for hardware-triggered events, the hardware "label" is a lot more relevant than any linux-internal representation.
Quoted 9 lines
Quoted 3 lines
All the user wants is to blink a led only for one of the LAN ports.
Denying it and using a more complex API doesn't make much sense, IMO.
As I see it you are the one wanting to introduce more complexity into the sysfs ABI. There is already a solution together with documentation and everything for when the user wants to "blink a led only for one of the LAN ports". It is the netdev trigger. And you want to complicate that ABI.
No. The existing in-kernel API is to blink a led based on software events originated from netdev from a single network port.
It could monitor an interface that doesn't physically exist (a virtual network interface, like tun0). It could even monitor traffic on a single VLAN, if the interface is specified like eth0.100[1].
[1] As we're discussing API here, I didn't test/check if the current implementation allows monitoring virtual and VLAN interfaces. From API's perspective, it makes perfect sense to be able to monitor any physical or logical interface supported by netdev.
The HW trigger is different: led blinks if the hardware detects Ethernet activity on one or more physical interfaces.
See, the netdev trigger monitors a netdev event with may or may not be due to an Ethernet port.
An Ethernet HW trigger monitors activity on a set of physical Ethernet ports.
In essence, the only thing in common is that both triggers are related to network, but they're actually monitoring two different types of events.
Merging them into a single one sounds a conceptual mistake on my eyes.
Quoted 2 lines
You say that for this controller it would be bad to do in SW, because it would take too much time in BIOS calls. (I wonder how much...)
Yeah, it would be interesting to know how much. However, measuring BIOS latency and time spent on such calls can be tricky: one needs to use a high-res clock that it is not used anywhere else, in order to measure it.
Thanks,
Mauro
Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
Hi!
Quoted 7 lines
Quoted 2 lines
You say that for this controller it would be bad to do in SW, because it would take too much time in BIOS calls. (I wonder how much...)
Yeah, it would be interesting to know how much. However, measuring BIOS latency and time spent on such calls can be tricky: one needs to use a high-res clock that it is not used anywhere else, in order to measure it.
I'm sure you can time kernel compilation while LEDs are using software netdev trigger, for example.
Pavel
Signature · 1 line
http://www.livejournal.com/~pavelmachek
Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
Em Wed, 26 May 2021 16:51:12 +0200
Pavel Machek <[email protected]> escreveu:
Quoted 5 lines
Hi!
Quoted 3 lines
Quoted 2 lines
You say that for this controller it would be bad to do in SW, because it would take too much time in BIOS calls. (I wonder how much...)
Quoted 7 lines
Quoted 4 lines
Yeah, it would be interesting to know how much. However, measuring BIOS latency and time spent on such calls can be tricky: one needs to use a high-res clock that it is not used anywhere else, in order to measure it.
I'm sure you can time kernel compilation while LEDs are using software netdev trigger, for example.
I can't see how. I mean, the problem is to monitor the impact of the BIOS call with may affect not only the application using the LED, but all other applications running at the same time on different CPUs, as the BIOS may want/need to lock other CPUs while its code is running, as it can't see the Linux CPU locks.
Thanks,
Mauro
Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
In reply to Mauro Carvalho ChehabHi!
Quoted 5 lines
Quoted 5 lines
Quoted 4 lines
See, there's nothing that the driver can possible do with rx, tx, link, interval, device_name/device, as the the BIOS allows to set to "LAN1", "LAN2" or "LAN1+LAN2". the WMI interface doesn't provide *any* information about what LAN1 means.
Quoted 8 lines
Quoted 4 lines
On the contrary, there is something the driver can do with these attributes. If the specific combination is not supported, the driver should return
-EOPNOTSUPPin thetrigger_offloadmethod and let the netdev trigger do the work in software.Letting netdev to trigger is something we don't want to allow, as this can cause side effects, making it doing slow the system due to BIOS calls for no good reason.
I'm with Marek here. Please listen to him.
Yes, operating LEDs can cost some CPU cycles. That's the case on most hardware. Yet we want to support most triggers on most hardware.
Best regards,
Pavel
Signature · 1 line
http://www.livejournal.com/~pavelmachek
Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
Em Wed, 26 May 2021 16:47:51 +0200
Pavel Machek <[email protected]> escreveu:
Quoted 12 lines
Hi!
Quoted 10 lines
Quoted 9 lines
Quoted 4 lines
See, there's nothing that the driver can possible do with rx, tx, link, interval, device_name/device, as the the BIOS allows to set to "LAN1", "LAN2" or "LAN1+LAN2". the WMI interface doesn't provide *any* information about what LAN1 means.
On the contrary, there is something the driver can do with these attributes. If the specific combination is not supported, the driver should return
-EOPNOTSUPPin thetrigger_offloadmethod and let the netdev trigger do the work in software.
Quoted 8 lines
Quoted 3 lines
Letting netdev to trigger is something we don't want to allow, as this can cause side effects, making it doing slow the system due to BIOS calls for no good reason.
I'm with Marek here. Please listen to him.
Yes, operating LEDs can cost some CPU cycles. That's the case on most hardware. Yet we want to support most triggers on most hardware.
There are two separate things here:
1. a "normal" LED operation can indeed take some CPU cycles, specially if done via an I2C bus. Yet, the Kernel will still be kept running. A BIOS call means that the Kernel will remain interrupted until when the BIOS decide to return control back to it. This affects all CPUs, and not just the one that would be busy setting the LED.
So, it is not a matter of just wasting some CPU cycles, but, instead, on potentially preventing all CPUs to run Kernel code, if the BIOS decides to lock until it finishes the LED setting and decides to return the control back to Linux.
In practice, depending on what workload is used, their real time requirements, and what the BIOS does (which may vary from device to device and on different BIOS versions) this could cause loses. This will mainly affect Real Time (e. g. audio and video apps).
A realistic test would be to make the LED blink as fast as possible, while a pro-Audio device using JACK outputs something using the smallest possible delay and see if blinking the leds would cause any audio issues.
While I'm not against allowing using a software-triggered event, as this *can* cause userspace problems, IMO the user needs to explicitly allow the usage of it and be aware when a software trigger is used. Letting the LEDs core or driver to fallback to software and cause disturbance at userspace doesn't sound right.
2. a netdev trigger monitors a different event than the hardware event.
See, if we have something like:
LAN1 ----> eno1 ---> eno1.100 # VLAN 100 traffic at eno1
port |
+-> eno1.200 # VLAN 200 traffic at eno1
LAN2 ---> enp5s0 ---> enp5s0.100 # VLAN 100 traffic at enp5s0
port |
+-> enp5s0.200 # VLAN 200 traffic at enp5s0 The hardware triggered event monitors a group of physical interfaces, e. g.:
- none
- LAN1
- LAN2
- both LAN1 and LAN2 (default)
The netdev trigger monitors software events at the network stack. So, it can be set to monitor a single software representation of an interface, e. g. it can monitor either:
- eno1
- enp5so
- eno1.100
- eno1.200
- enp5s0.100
- enp5so.200
It doesn't allow monitoring multiple interfaces at the same time.
So, it is not possible to monitor both eno1 and enp5so.
Also, even if it would be possible, what hardware detects could be different than what the network stack detects.
On other words, even if we keep the BIOS default on front2 led and set front3 led to netdev trigger for eno1 they will blink differently, as one would be monitoring a single interface and another one will monitor both.
It will even more different if netdev is set to monitor, let's say, VLAN 100 traffic at eno1.100, while hw trigger is set to LAN1+LAN2.
Thanks,
Mauro
PATCH v2 17/17 leds: leds-nuc: add support for changing the power limit scheme
In reply to Mauro Carvalho ChehabThe power limit indicator may have 2 behaviors:
1. Its color gradually changes from green to red;
2. It displays a single color
Add support for it.
--- drivers/leds/leds-nuc.c | 93 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+)
drivers/leds/leds-nuc.c +93 -0
@@ -1767,6 +1767,8 @@ static ssize_t store_ethernet_type(struct device *dev,if (!nuc_wmi_test_control (dev, led, ctrl))return -ENODEV ; + tmp =strsep ((char **)&buf,"\n" ); +for (val =0 ; val <ARRAY_SIZE (ethernet_type); val++)if (!strcasecmp (tmp, ethernet_type[val]))break ;
@@ -1786,12 +1788,102 @@ static ssize_t store_ethernet_type(struct device *dev,return len; } +/* Power Limit Indication scheme */ +static const char *const power_limit_scheme[] = { +"green to red" , +"single color" +}; + +static ssize_t show_power_limit_scheme (struct device *dev, +struct device_attribute *attr, +char *buf) +{ +struct led_classdev *cdev =dev_get_drvdata (dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); +u8 input[NUM_INPUT_ARGS ] = {0 }; +u8 output[NUM_OUTPUT_ARGS ]; +int ctrl, ret, val, i, n; +int size =PAGE_SIZE ; +char *p = buf; + +if (led->indicator !=LED_IND_POWER_LIMIT ) +return -EINVAL ; + + ctrl = led->reg_table[led->indicator][LED_FUNC_POWER_STATE_NUM_CTRLS ]; + +if (!nuc_wmi_test_control (dev, led, ctrl)) +return -ENODEV ; + + input[0 ] =LED_NEW_GET_CONTROL_ITEM ; + input[1 ] = led->id; + input[2 ] = led->indicator; + input[3 ] = ctrl; + + ret =nuc_nmi_cmd (dev,LED_NEW_GET_STATUS , input, output); +if (ret) +return ret; + + val = output[0 ]; + +for (i =0 ; i <ARRAY_SIZE (power_limit_scheme); i++) { +if (i == val) + n =scnprintf (p, size,"[%s] " , power_limit_scheme[i]); +else + n =scnprintf (p, size,"%s " , power_limit_scheme[i]); + p += n; + size -= n; + } + size -=scnprintf (p, size,"\n" ); + +return PAGE_SIZE - size; +} + +static ssize_t store_power_limit_scheme (struct device *dev, +struct device_attribute *attr, +const char *buf,size_t len) +{ +struct led_classdev *cdev =dev_get_drvdata (dev); + struct nuc_nmi_led *led = container_of(cdev, struct nuc_nmi_led, cdev); +u8 input[NUM_INPUT_ARGS ] = {0 }; +int ctrl, val, ret; +const char *tmp; + +if (led->indicator !=LED_IND_POWER_LIMIT ) +return -EINVAL ; + + ctrl = led->reg_table[led->indicator][LED_FUNC_POWER_STATE_NUM_CTRLS ]; + +if (!nuc_wmi_test_control (dev, led, ctrl)) +return -ENODEV ; + + tmp =strsep ((char **)&buf,"\n" ); + +for (val =0 ; val <ARRAY_SIZE (power_limit_scheme); val++) +if (!strcasecmp (tmp, power_limit_scheme[val])) +break ; + +if (val >=ARRAY_SIZE (power_limit_scheme)) +return -EINVAL ; + + input[0 ] = led->id; + input[1 ] = led->indicator; + input[2 ] = ctrl; + input[3 ] = val; + + ret =nuc_nmi_cmd (dev,LED_SET_VALUE , input,NULL ); +if (ret) +return ret; + +return len; +} +static LED_ATTR_RW (indicator);static LED_ATTR_RW (color);static LED_ATTR_RW (blink_behavior);static LED_ATTR_RW (blink_frequency);static LED_ATTR_RW (hdd_default);static LED_ATTR_RW (ethernet_type); +static LED_ATTR_RW (power_limit_scheme);LED_ATTR_POWER_STATE_RW (s0_brightness, brightness,0 );LED_ATTR_POWER_STATE_RW (s0_blink_behavior, blink_behavior,0 );
@@ -1821,6 +1913,7 @@ static struct attribute *nuc_wmi_led_attr[] = { &dev_attr_indicator.attr, &dev_attr_hdd_default.attr, &dev_attr_ethernet_type.attr, + &dev_attr_power_limit_scheme.attr, NULL, };
Signature · 1 line
2.31.1
Hi!
Quoted 6 lines
Some models come with single colored or dual-colored LEDs, but high end models have RGB LEDs.
Programming them can ether be done via BIOS or by the OS, however, BIOS settings are limited. So, the vendor offers a Windows application that allows to fully use the functionality provided by the
firmware/hardware.
I'm not sure why you are submitting v2 in the middle of interface discussion.
Marek and I are saying the same thing -- this needs to use close to existing APIs.
If you want to get something merged quickly, please submit basic functionality only (toggling the LED on/off) that completely fits existing APIs. We can review that.
Best regards,
Pavel
Signature · 1 line
http://www.livejournal.com/~pavelmachek
Em Wed, 19 May 2021 13:11:07 +0200
Pavel Machek <[email protected]> escreveu:
Quoted 11 lines
Hi!
Quoted 6 lines
Some models come with single colored or dual-colored LEDs, but high end models have RGB LEDs.
Programming them can ether be done via BIOS or by the OS, however, BIOS settings are limited. So, the vendor offers a Windows application that allows to fully use the functionality provided by the
firmware/hardware.I'm not sure why you are submitting v2 in the middle of interface discussion.
I'll refrain sending a new version while we're discussing the interface.
Quoted 2 lines
Marek and I are saying the same thing -- this needs to use close to existing APIs.
Ok, but I'm not seeing an existing API that provides what those LEDs need.
Quoted 3 lines
If you want to get something merged quickly, please submit basic functionality only (toggling the LED on/off) that completely fits existing APIs. We can review that.
If you prefer working this way, I can send an initial patch with just the very basic. Actually, if you apply just patch 2 of this series, it will provide support for for just setting the brightness on NUC8.
However, the main reason why someone (including myself) want this driver is to allow to dynamically change what hardware event will be triggering the LED and how, and if suspend will blink or not[1].
Being able to also change the LED color is a plus.
[1] Disabling blink at suspend/hibernate is one of the things that I use here: as the machine is at my bedroom, I don't want it to be blinking all night long when the machine is sleeping :-)
Thanks,
Mauro
Hi!
Quoted 5 lines
Quoted 2 lines
Marek and I are saying the same thing -- this needs to use close to existing APIs.
Ok, but I'm not seeing an existing API that provides what those LEDs need.
Well, there "close to" part comes into play.
Quoted 8 lines
Quoted 3 lines
If you want to get something merged quickly, please submit basic functionality only (toggling the LED on/off) that completely fits existing APIs. We can review that.
If you prefer working this way, I can send an initial patch with just the very basic. Actually, if you apply just patch 2 of this series, it will provide support for for just setting the brightness on NUC8.
I don't care much. We can discuss minimal interface additions neccessary to support your usecases.
But what you proposed was nowhere near close.
Note that we don't want to support every crazy feature, just because hardware can do it.
Quoted 5 lines
However, the main reason why someone (including myself) want this driver is to allow to dynamically change what hardware event will be triggering the LED and how, and if suspend will blink or not[1].
Being able to also change the LED color is a plus.
This one is hard if the LED does not support full color.
Quoted 3 lines
[1] Disabling blink at suspend/hibernate is one of the things that I use here: as the machine is at my bedroom, I don't want it to be blinking all night long when the machine is sleeping :-)
Ok, so lets start with the blink at suspend thing?
Having power LED on when machine is on, and slowly "breathing" when machine is suspended is something I have seen before. Is that what your hardware is doing?
Best regards,
Pavel
Signature · 1 line
http://www.livejournal.com/~pavelmachek
Em Wed, 19 May 2021 21:41:15 +0200
Pavel Machek <[email protected]> escreveu:
Quoted 19 lines
Hi!
Quoted 5 lines
Quoted 2 lines
Marek and I are saying the same thing -- this needs to use close to existing APIs.
Ok, but I'm not seeing an existing API that provides what those LEDs need.
Well, there "close to" part comes into play.
Quoted 8 lines
Quoted 3 lines
If you want to get something merged quickly, please submit basic functionality only (toggling the LED on/off) that completely fits existing APIs. We can review that.
If you prefer working this way, I can send an initial patch with just the very basic. Actually, if you apply just patch 2 of this series, it will provide support for for just setting the brightness on NUC8.
Quoted 4 lines
I don't care much. We can discuss minimal interface additions neccessary to support your usecases.
But what you proposed was nowhere near close.
Ok. Let's try to come into an agreement about what's needed.
On my discussions with Marek, it sounds to me that the features from the trigger API won't fit, as the attributes there won't be supported by the NUC leds (and vice-versa).
Yet, we could try to have something that would look similar.
Quoted 3 lines
Note that we don't want to support every crazy feature, just because hardware can do it.
Neither do I ;-)
I don't care much for Software controlled LEDs, nor for a feature that would allow the BIOS to "hide" the LED colors as if it were a single colored one, for instance.
Yet, the remaining stuff seems pretty much OK.
Quoted 8 lines
Quoted 6 lines
However, the main reason why someone (including myself) want this driver is to allow to dynamically change what hardware event will be triggering the LED and how, and if suspend will blink or not[1].
Being able to also change the LED color is a plus.
This one is hard if the LED does not support full color.
Only a subset of devices support RGB colors, but the API has support for dual-color and 8-color LEDs. On those, the color is selected like an enum.
The NUC8 device I use her has RGB color LEDs.
Quoted 6 lines
Quoted 4 lines
[1] Disabling blink at suspend/hibernate is one of the things that I use here: as the machine is at my bedroom, I don't want it to be blinking all night long when the machine is sleeping :-)
Ok, so lets start with the blink at suspend thing?
Yeah, that sounds to be a good start point.
Quoted 4 lines
Having power LED on when machine is on, and slowly "breathing" when machine is suspended is something I have seen before. Is that what your hardware is doing?
Yes, but see: my device has 6 leds (API supports up to 7 leds).
Any of them can be programmed to act as a power LED at runtime.
So, the first thing that the API needs is a way to tell what LED is monitoring the device's power state.
Then, for each power state (S0, S3, S5), define if the LED will be ON all the times or not.
The "slowing breathing" is one of the possible blink patterns. The driver supports 4 other blink patterns
- Solid - the LED won't blink;
- Breathing - it looks like a sinusoidal wave pattern;
- Pulsing - it looks like a square wave pattern;
- Strobing - it turns ON suddenly, and then it slowly turns OFF.
The speed of the blink is also adjustable, ranging from 0.1 Hz to 1 Hz, on 0.1 Hz steps.
---
Let me explain this specific part of the API from my original proposal.
Those are the led names from the datasheets (NUC 8 and above), and my proposal for the sysfs class directory name:
============= ===============================
LED name sysfs
============= ===============================
Skull ``/sys/class/leds/nuc::skull``
Skull eyes ``/sys/class/leds/nuc::eyes``
Power ``/sys/class/leds/nuc::power``
HDD ``/sys/class/leds/nuc::hdd``
Front1 ``/sys/class/leds/nuc::front1``
Front2 ``/sys/class/leds/nuc::front2``
Front3 ``/sys/class/leds/nuc::front3``
============= ===============================
For each of the above, there's the need to identify what hardware function is monitored (if any).
My proposal were to add an "indicator" node (the name came from the Intel datasheets) that shows what led will monitor the power state.
Then, one blink_behavior and one blink_frequency per power state, e. g.:
/sys/class/leds/nuc::front1
|-- indicator
|-- s0_blink_behavior
|-- s0_blink_frequency
|-- s3_blink_behavior
|-- s3_blink_frequency
|-- s5_blink_behavior
`-- s5_blink_frequencyPS.: I don't care much about what names we'll use. Feel free to rename them, if you think the above is not clear or generic enough.
-
To make part of the API complete, there's also the need of a node to control the max brightness that the leds will achieve at the ON state, and another one to control the color on each state, as one could define, let's say, "white" when powered on, "blue" when suspended and "yellow" when hibernating. The colors at the NUC I have are RGB (but other models can use an enum for the supported colors).
/sys/class/leds/nuc::front1
|-- s0_brightness
|-- s0_color # only shown on colored leds
|-- s3_brightness
|-- s3_color # only shown on colored leds
|-- s0_brightness
`-- s5_color # only shown on colored ledsThanks,
Mauro
Quoted 2 lines · On Thu, 20 May 2021 01:07:20 +0200 Mauro Carvalho Chehab…
So, the first thing that the API needs is a way to tell what LED is monitoring the device's power state.
If a LED can monitor the device's power state in HW, register a LED private trigger for this LED. If the LED is configured into this state by default, you can set this trigger to be the default_trigger prior registering the LED. The name of this private trigger can be "hw:powerstate" or something like that (I wonder what others will think about this name).
Quoted 6 lines
Then, for each power state (S0, S3, S5), define if the LED will be ON all the times or not.
The "slowing breathing" is one of the possible blink patterns. The driver supports 4 other blink patterns
Quoted 7 lines
- Solid - the LED won't blink;
- Breathing - it looks like a sinusoidal wave pattern;
- Pulsing - it looks like a square wave pattern;
- Strobing - it turns ON suddenly, and then it slowly turns OFF.The speed of the blink is also adjustable, ranging from 0.1 Hz to 1 Hz, on 0.1 Hz steps.
Is the speed of breathing/strobing also adjustable? Or only when pulsing?
When this "hw:powerstate" trigger is enabled for this LED, only then another sysfs files should appear in this LED's sysfs directory.
Quoted 28 lines
---
Let me explain this specific part of the API from my original proposal.
Those are the led names from the datasheets (NUC 8 and above), and my proposal for the sysfs class directory name:
============= ===============================
LED name sysfs
============= ===============================
Skull ``/sys/class/leds/nuc::skull``
Skull eyes ``/sys/class/leds/nuc::eyes``
Power ``/sys/class/leds/nuc::power``
HDD ``/sys/class/leds/nuc::hdd``
Front1 ``/sys/class/leds/nuc::front1``
Front2 ``/sys/class/leds/nuc::front2``
Front3 ``/sys/class/leds/nuc::front3``
============= ===============================For each of the above, there's the need to identify what hardware function is monitored (if any).
My proposal were to add an "indicator" node (the name came from the Intel datasheets) that shows what led will monitor the power state.
Then, one
blink_behaviorand oneblink_frequencyper power state, e. g.:
Quoted 8 lines
/sys/class/leds/nuc::front1
|-- indicator
|-- s0_blink_behavior
|-- s0_blink_frequency
|-- s3_blink_behavior
|-- s3_blink_frequency
|-- s5_blink_behavior
`-- s5_blink_frequencyI'd rather use one file for frequencies and one for intervals, and map in to an array, but that is just my preference...
Quoted 14 lines
PS.: I don't care much about what names we'll use. Feel free to rename them, if you think the above is not clear or generic enough.
-
To make part of the API complete, there's also the need of a node to control the max brightness that the leds will achieve at the ON state, and another one to control the color on each state, as one could define, let's say, "white" when powered on, "blue" when suspended and "yellow" when hibernating. The colors at the NUC I have are RGB (but other models can use an enum for the supported colors).
Quoted 7 lines
/sys/class/leds/nuc::front1
|-- s0_brightness
|-- s0_color # only shown on colored leds
|-- s3_brightness
|-- s3_color # only shown on colored leds
|-- s0_brightness
`-- s5_color # only shown on colored ledsIf the BIOS reports a LED being full RGB LED, you should register it via multicolor framework. Regarding the enum with 8 colors: are these colors red, yellow, green, cyan, blue, magenta? Because if so, then this is RGB with each channel being binary :) So you can again use multicolor framework.
Em Thu, 20 May 2021 18:19:19 +0200
Marek Behún <[email protected]> escreveu:
Quoted 6 lines
Quoted 2 lines · On Thu, 20 May 2021 01:07:20 +0200 Mauro Carvalho Chehab…
So, the first thing that the API needs is a way to tell what LED is monitoring the device's power state.
Quoted 6 lines
If a LED can monitor the device's power state in HW, register a LED private trigger for this LED. If the LED is configured into this state by default, you can set this trigger to be the
default_triggerprior registering the LED. The name of this private trigger can be "hw:powerstate" or something like that (I wonder what others will think about this name).
Ok.
So, assuming that we will have one trigger per each hardware state, it could have something like (names subject to change):
- hw:powerstate
- hw:disk_activity
- hw:ethernet_activity
- hw:wifi_active
- hw:power_limit
Right?
It still needs to indicate two other possible states:
- software controlled led;
- led is disabled.
Setting led's brightness to zero is different than disabling it.
Disabling can be done via BIOS, but BIOS config doesn't allow setting the brightness. There are other difference on BIOS settings:
it allow disabling each/all LED controls and/or to disable software control of each LED.
So, we need a way at the API to uniquely identify when the LED is software-controlled and when it is disabled. Would it be something like:
- hw:disable
trigger? or better to implement it on a different way?
Quoted 11 lines
Quoted 11 lines
Then, for each power state (S0, S3, S5), define if the LED will be ON all the times or not.
The "slowing breathing" is one of the possible blink patterns. The driver supports 4 other blink patterns
- Solid - the LED won't blink;
- Breathing - it looks like a sinusoidal wave pattern;
- Pulsing - it looks like a square wave pattern;
- Strobing - it turns ON suddenly, and then it slowly turns OFF.
Quoted 5 lines
Quoted 2 lines
The speed of the blink is also adjustable, ranging from 0.1 Hz to 1 Hz, on 0.1 Hz steps.
Is the speed of breathing/strobing also adjustable? Or only when pulsing?
Yes, speed is also adjustable, from 0.1 to 1.0 HZ, in 0.1 Hz (NUC 8 and above).
The NUC6 API is more limited than NUC8+: it has just two blink patterns (blink, fade), and only 3 frequencies are allowed (0.25 Hz, 0.50 Hz and 1.0 Hz).
Quoted 3 lines
When this "hw:powerstate" trigger is enabled for this LED, only then another sysfs files should appear in this LED's sysfs directory.
OK, makes sense.
Out of curiosity: is it reliable to make sysfs nodes appear and disappear dynamically? Does inotify (or something similar) can be used to identify when such nodes appear/disappear?
I remember a long time ago I wanted to use something like that at the media (or edac?) subsystem, but someone (Greg, I think) recommended otherwise due to some potential racing issues.
Quoted 38 lines
Quoted 29 lines
---
Let me explain this specific part of the API from my original proposal.
Those are the led names from the datasheets (NUC 8 and above), and my proposal for the sysfs class directory name:
============= ===============================
LED name sysfs
============= ===============================
Skull ``/sys/class/leds/nuc::skull``
Skull eyes ``/sys/class/leds/nuc::eyes``
Power ``/sys/class/leds/nuc::power``
HDD ``/sys/class/leds/nuc::hdd``
Front1 ``/sys/class/leds/nuc::front1``
Front2 ``/sys/class/leds/nuc::front2``
Front3 ``/sys/class/leds/nuc::front3``
============= ===============================For each of the above, there's the need to identify what hardware function is monitored (if any).
My proposal were to add an "indicator" node (the name came from the Intel datasheets) that shows what led will monitor the power state.
Then, one
blink_behaviorand oneblink_frequencyper power state, e. g.:Quoted 8 lines
/sys/class/leds/nuc::front1 |-- indicator |-- s0_blink_behavior |-- s0_blink_frequency |-- s3_blink_behavior |-- s3_blink_frequency |-- s5_blink_behavior `-- s5_blink_frequency
Quoted 2 lines
I'd rather use one file for frequencies and one for intervals, and map in to an array, but that is just my preference...
By intervals are you meaning 1/frequency? So, basically exposing the frequency as two fields? If so, it sounds overkill to me to have both.
Btw, maybe instead of "blink_behavior" it could use "blink_pattern".
This would diverge from the datahseet name, but it probably describes better what will be controlled when blink is enabled:
- frequency (or inverval)
- pattern
Quoted 23 lines
Quoted 15 lines
PS.: I don't care much about what names we'll use. Feel free to rename them, if you think the above is not clear or generic enough.
-
To make part of the API complete, there's also the need of a node to control the max brightness that the leds will achieve at the ON state, and another one to control the color on each state, as one could define, let's say, "white" when powered on, "blue" when suspended and "yellow" when hibernating. The colors at the NUC I have are RGB (but other models can use an enum for the supported colors).
Quoted 7 lines
/sys/class/leds/nuc::front1 |-- s0_brightness |-- s0_color # only shown on colored leds |-- s3_brightness |-- s3_color # only shown on colored leds |-- s0_brightness `-- s5_color # only shown on colored leds
Quoted 2 lines
If the BIOS reports a LED being full RGB LED, you should register it via multicolor framework.
OK.
Quoted 4 lines
Regarding the enum with 8 colors: are these
colors red, yellow, green, cyan, blue, magenta? Because if so, then this is RGB with each channel being binary :) So you can again use multicolor framework.
The dual-colored ones aren't RGB. Two types are supported:
- Blue/Amber
- Blue/White
the only one with 8 colors is at NUC6 API: the ring led. This one can be mapped as RGB with 1 bit per color, as those are the colors:
+---------+
| disable |
+---------+
| cyan |
+---------+
| pink |
+---------+
| yellow |
+---------+
| blue |
+---------+
| red |
+---------+
| green |
+---------+
| white |
+---------+Thanks,
Mauro
Quoted 10 lines · On Thu, 20 May 2021 21:16:15 +0200 Mauro Carvalho Chehab…
So, assuming that we will have one trigger per each hardware state, it could have something like (names subject to change):
- hw:powerstate
- hw:disk_activity
- hw:ethernet_activity
- hw:wifi_active
- hw:power_limitRight?
Yes, but we should really try to map ethernet_activity to netdev and disk_activity to a potential blkdev trigger :-) That's my opinion.
Quoted 13 lines
It still needs to indicate two other possible states:
- software controlled led;
- led is disabled.Setting led's brightness to zero is different than disabling it.
Disabling can be done via BIOS, but BIOS config doesn't allow setting the brightness. There are other difference on BIOS settings:
it allow disabling each/all LED controls and/or to disable software control of each LED.
Quoted 7 lines
So, we need a way at the API to uniquely identify when the LED is software-controlled and when it is disabled. Would it be something like:
- hw:disable
trigger? or better to implement it on a different way?
What is the functional difference (visible to the user) between zero brightness and disabled LED? IMO if user says
echo 0 >brightness
you can just disable the LED. Or is this impossible?
Quoted 16 lines
Quoted 2 lines
Is the speed of breathing/strobing also adjustable? Or only when pulsing?
Yes, speed is also adjustable, from 0.1 to 1.0 HZ, in 0.1 Hz (NUC 8 and above).
The NUC6 API is more limited than NUC8+: it has just two blink patterns (blink, fade), and only 3 frequencies are allowed (0.25 Hz, 0.50 Hz and 1.0 Hz).
Quoted 3 lines
When this "hw:powerstate" trigger is enabled for this LED, only then another sysfs files should appear in this LED's sysfs directory.
OK, makes sense.
Quoted 7 lines
Out of curiosity: is it reliable to make sysfs nodes appear and disappear dynamically? Does inotify (or something similar) can be used to identify when such nodes appear/disappear?
I remember a long time ago I wanted to use something like that at the media (or edac?) subsystem, but someone (Greg, I think) recommended otherwise due to some potential racing issues.
No idea, but I would guess yes.
Quoted 5 lines
Quoted 2 lines
I'd rather use one file for frequencies and one for intervals, and map in to an array, but that is just my preference...
By intervals are you meaning 1/frequency? So, basically exposing the frequency as two fields? If so, it sounds overkill to me to have both.
Sorry, I meant one file for frequencies and one for patterns.
Quoted 9 lines
Btw, maybe instead of "
blink_behavior" it could use "blink_pattern".This would diverge from the datahseet name, but it probably describes better what will be controlled when blink is enabled:
- frequency (or inverval)
- pattern
Quoted 8 lines
Quoted 4 lines
Regarding the enum with 8 colors: are these
colors red, yellow, green, cyan, blue, magenta? Because if so, then this is RGB with each channel being binary :) So you can again use multicolor framework.The dual-colored ones aren't RGB. Two types are supported:
- Blue/Amber
- Blue/White
These would need a new API, ignore these for now.
Marek
Em Thu, 20 May 2021 21:43:56 +0200
Marek Behún <[email protected]> escreveu:
Quoted 34 lines
Quoted 10 lines · On Thu, 20 May 2021 21:16:15 +0200 Mauro Carvalho Chehab…
So, assuming that we will have one trigger per each hardware state, it could have something like (names subject to change):
- hw:powerstate
- hw:disk_activity
- hw:ethernet_activity
- hw:wifi_active
- hw:power_limitRight?
Yes, but we should really try to map
ethernet_activityto netdev anddisk_activityto a potential blkdev trigger :-) That's my opinion.Quoted 17 lines
It still needs to indicate two other possible states:
- software controlled led;
- led is disabled.Setting led's brightness to zero is different than disabling it.
Disabling can be done via BIOS, but BIOS config doesn't allow setting the brightness. There are other difference on BIOS settings:
it allow disabling each/all LED controls and/or to disable software control of each LED.So, we need a way at the API to uniquely identify when the LED is software-controlled and when it is disabled. Would it be something like:
Quoted 8 lines
Quoted 3 lines
- hw:disable
trigger? or better to implement it on a different way?
What is the functional difference (visible to the user) between zero brightness and disabled LED? IMO if user says
echo 0 >brightness
you can just disable the LED. Or is this impossible?
echo 0 >brightness will turn off the LED, but it won't disable it. A trigger can still be enabled on it.
With a disabled LED, depending on how it was disabled, it can't be enabled in runtime. One may need to boot the machine and use BIOS setup to enable it. Trying to change such LED in runtime will return an error.
Quoted 27 lines
Quoted 17 lines
Quoted 3 lines
Is the speed of breathing/strobing also adjustable? Or only when pulsing?
Yes, speed is also adjustable, from 0.1 to 1.0 HZ, in 0.1 Hz (NUC 8 and above).
The NUC6 API is more limited than NUC8+: it has just two blink patterns (blink, fade), and only 3 frequencies are allowed (0.25 Hz, 0.50 Hz and 1.0 Hz).
Quoted 3 lines
When this "hw:powerstate" trigger is enabled for this LED, only then another sysfs files should appear in this LED's sysfs directory.
OK, makes sense.
Quoted 7 lines
Out of curiosity: is it reliable to make sysfs nodes appear and disappear dynamically? Does inotify (or something similar) can be used to identify when such nodes appear/disappear?
I remember a long time ago I wanted to use something like that at the media (or edac?) subsystem, but someone (Greg, I think) recommended otherwise due to some potential racing issues.
No idea, but I would guess yes.
Quoted 7 lines
Quoted 5 lines
Quoted 2 lines
I'd rather use one file for frequencies and one for intervals, and map in to an array, but that is just my preference...
By intervals are you meaning 1/frequency? So, basically exposing the frequency as two fields? If so, it sounds overkill to me to have both.
Sorry, I meant one file for frequencies and one for patterns.
Ah, makes sense. Yeah, that's how I mapped it.
Quoted 13 lines
Quoted 13 lines
Btw, maybe instead of "
blink_behavior" it could use "blink_pattern".This would diverge from the datahseet name, but it probably describes better what will be controlled when blink is enabled:
- frequency (or inverval)
- patternQuoted 4 lines
Regarding the enum with 8 colors: are these
colors red, yellow, green, cyan, blue, magenta? Because if so, then this is RGB with each channel being binary :) So you can again use multicolor framework.
Quoted 5 lines
Quoted 3 lines
The dual-colored ones aren't RGB. Two types are supported:
- Blue/Amber
- Blue/WhiteThese would need a new API, ignore these for now.
This affects mainly NUC6 part of the API. I'll postpone it.
Yet, IMHO, the best here is to do exactly how I did: use the "normal" leds class and add a "color" attribute that can either be "blue" or "amber" written on it (for a blue/amber kind of LED).
Thanks,
Mauro