linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC
@ 2021-05-18 15:08 Mauro Carvalho Chehab
  2021-05-18 15:08 ` [PATCH v2 01/17] docs: describe the API used to set NUC LEDs Mauro Carvalho Chehab
                   ` (17 more replies)
  0 siblings, 18 replies; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-18 15:08 UTC (permalink / raw)
  Cc: linuxarm, mauro.chehab, Mauro Carvalho Chehab, gregkh,
	Pavel Machek, linux-doc, linux-kernel, linux-leds

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

-- 
2.31.1



^ permalink raw reply	[flat|nested] 44+ messages in thread

* [PATCH v2 01/17] docs: describe the API used to set NUC LEDs
  2021-05-18 15:08 [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC Mauro Carvalho Chehab
@ 2021-05-18 15:08 ` Mauro Carvalho Chehab
  2021-05-18 15:08 ` [PATCH v2 02/17] leds: add support for NUC WMI LEDs Mauro Carvalho Chehab
                   ` (16 subsequent siblings)
  17 siblings, 0 replies; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-18 15:08 UTC (permalink / raw)
  Cc: linuxarm, mauro.chehab, Mauro Carvalho Chehab, Dan Murphy,
	Greg Kroah-Hartman, Jacek Anaszewski, Jonathan Corbet, linux-doc,
	linux-kernel

Some NUC6 have LEDs that can be configurated dynamically from
the operational system, via WMI.

Describe how the API for such devices should work.

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
 Documentation/leds/index.rst    |   1 +
 Documentation/leds/leds-nuc.rst | 447 ++++++++++++++++++++++++++++++++
 2 files changed, 448 insertions(+)
 create mode 100644 Documentation/leds/leds-nuc.rst

diff --git a/Documentation/leds/index.rst b/Documentation/leds/index.rst
index e5d63b940045..4fdf9b60bb86 100644
--- a/Documentation/leds/index.rst
+++ b/Documentation/leds/index.rst
@@ -25,4 +25,5 @@ LEDs
    leds-lp5562
    leds-lp55xx
    leds-mlxcpld
+   leds-nuc
    leds-sc27xx
diff --git a/Documentation/leds/leds-nuc.rst b/Documentation/leds/leds-nuc.rst
new file mode 100644
index 000000000000..02e1c2602dd3
--- /dev/null
+++ b/Documentation/leds/leds-nuc.rst
@@ -0,0 +1,447 @@
+==================
+Intel NUC WMI LEDs
+==================
+
+Some models of the Intel Next Unit of Computing (NUC) may have programmable
+LEDs. Those can be partially programmed by opening the BIOS configuration.
+
+Among those models, some of them also allows the Operational System to
+adjust the LED parameters via a firmware interface, called Windows Management
+Interface - WMI.
+
+There are currently three different versions of WMI API for NUC, depending
+on the NUC generation:
+
+For NUC 6 and 7, the WMI API is defined at:
+
+   - https://www.intel.com/content/www/us/en/support/articles/000023426/intel-nuc/intel-nuc-kits.html
+
+For NUC 8 and 8, the WMI API is defined at:
+
+  - https://raw.githubusercontent.com/nomego/intel_nuc_led/master/specs/INTEL_WMI_LED_0.64.pdf
+
+For NUC 10 and newer generations, the WMI API is defined at:
+  - https://www.intel.com/content/dam/support/us/en/documents/intel-nuc/WMI-Spec-Intel-NUC-NUC10ixFNx.pdf
+
+The nuc-wmi driver provides userspace support for changing the LED
+configuration, and supports WMI API since NUC 6. Yet, the NUC6 WMI API
+functionality is limited when compared with the newer NUC generations.
+
+Although there are some internal differences, the features supported for
+NUC 10 WMI API are almost identical to the ones supported by NUC 8 WMI API.
+
+.. note::
+
+   Even when the firmware supports setting LEDs via WMI API, the
+   BIOS configuration has some parameters to either allow the Operational
+   System to also control them. Instructions about how to enable it can
+   be found at the manual of each specific NUC model.
+
+NUC 6 and NUC 7
+===============
+
+When the driver detects NUC LEDs, up to two directories will be created
+under sysfs. They're asocciated with the button(s) named "Power" and "Ring".
+
+Assuming that sysfs is mounted under ``/sys``, those are the
+directories:
+
+=============	==============================
+LED name	sysfs device directory
+=============	==============================
+Power		``/sys/class/leds/nuc::power``
+Ring		``/sys/class/leds/nuc::ring``
+=============	==============================
+
+For each of the above directory, some sysfs nodes will allow to control the
+functionality for each button::
+
+    .
+    |-- blink_behavior
+    |-- blink_frequency
+    |-- brightness
+    |-- color
+    |-- device -> ../../../8C5DA44C-CDC3-46B3-8619-4E26D34390B7
+    `-- max_brightness
+
+.. note::
+
+   1. any user can read the LEDs parameter;
+   2. changing a LED parameter is limited to the owner of the sysfs device
+      nodes (usually, the ``root`` user);
+   3. changing a LED parameter is case-insensitive
+
+Brightness
+----------
+
+The ``brightness`` adjusts the LED brightness, and can be set from
+0 to ``max_brightness``.
+
+So, for instance, in order to put the power LED with 50% of the bright::
+
+    $ cat /sys/class/leds/nuc::power/max_brightness
+    100
+    # echo 50 > /sys/class/leds/nuc::power/max_brightness
+
+Color
+-----
+
+On NUC6 API, the power LED color can be be dual colored. Those are
+the valid color values:
+
+    +---------+
+    | disable |
+    +---------+
+    | blue    |
+    +---------+
+    | amber   |
+    +---------+
+
+And the ring LED color can be multi colored. Those are the valid color
+values:
+
+    +---------+
+    | disable |
+    +---------+
+    | cyan    |
+    +---------+
+    | pink    |
+    +---------+
+    | yellow  |
+    +---------+
+    | blue    |
+    +---------+
+    | red     |
+    +---------+
+    | green   |
+    +---------+
+    | white   |
+    +---------+
+
+Changing the ring color of the ring LED can be done with::
+
+    $ cat /sys/class/leds/nuc::ring/color
+    [disable]  cyan  pink  yellow  blue  red  green  white
+    # echo "cyan" > /sys/class/leds/nuc::ring/color
+
+Blink behavior and frequency
+----------------------------
+
+The NUC6 API supports those blink behaviors:
+
+    +-------+
+    | Solid |
+    +-------+
+    | Blink |
+    +-------+
+    | Fade  |
+    +-------+
+
+
+When in blink and/or fade mode, it supports the following frequencies:
+
+    +---------+
+    | 1 Hz    |
+    +---------+
+    | 0.5 Hz  |
+    +---------+
+    | 0.25 Hz |
+    +---------+
+
+Changing the blink behavior of the power LED, for instance, can be done
+with::
+
+    $ cat /sys/class/leds/nuc::power/blink_behavior
+    [solid] blink  fade
+    $ cat /sys/class/leds/nuc::power/blink_frequency
+    [1]  0.5  0.25
+    # echo "blink" > /sys/class/leds/nuc::power/blink_behavior
+    # echo 0.5 > /sys/class/leds/nuc::power/blink_frequency
+
+.. note::
+
+   The blink/fade behavior and frequencies can support only a subset of
+   the above values on old BIOS.
+
+NUC 8 and newer generations
+===========================
+
+When the driver detects NUC LEDs,  up to seven directories will be
+created under sysfs. Each one for each different LED.
+
+Assuming that sysfs is mounted under ``/sys``, those are the
+directories:
+
+=============	===============================
+LED name	sysfs device node
+=============	===============================
+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 directory, some sysfs nodes will allow to control the
+functionality for each button::
+
+    /sys/class/leds/nuc::front1
+    |-- blink_behavior
+    |-- blink_frequency
+    |-- brightness
+    |-- color
+    |-- ethernet_type
+    |-- hdd_default
+    |-- indicator
+    |-- max_brightness
+    |-- power_limit_scheme
+    |-- ready_mode_blink_behavior
+    |-- ready_mode_blink_frequency
+    |-- ready_mode_brightness
+    |-- s0_blink_behavior
+    |-- s0_blink_frequency
+    |-- s0_brightness
+    |-- s3_blink_behavior
+    |-- s3_blink_frequency
+    |-- s3_brightness
+    |-- s5_blink_behavior
+    |-- s5_blink_frequency
+    `-- s5_brightness
+
+The sessions below will explain the meaning of each aspect of the API.
+
+.. note::
+
+   1. any user can read the LEDs parameter;
+   2. changing a LED parameter is limited to the owner of the sysfs device
+      nodes (usually, the ``root`` user);
+   3. changing a LED parameter is case-insensitive;
+   4. The LED ``indicator`` parameter controls the function of the LED.
+      All other parameters can be enabled or disabled in runtime, depending
+      on it. When a certain parameter is disabled, an error code will be
+      returned;
+   5. The hardware and its firmware actually controls the LED. The interface
+      provided by the driver just changes the LED settings in runtime.
+      Such changes can persist even after rebooting.
+
+LED indicator
+-------------
+
+Despite the LED's name, the LED API may allow them to indicate different
+hardware events.
+
+This is controlled via the ``indicator`` device node. Reading from it displays
+all the supported events for a giving LED, and the currently ative one::
+
+    $ cat /sys/class/leds/nuc::front1/indicator
+    Power State  [HDD Activity]  Ethernet  WiFi  Software  Power Limit  Disable
+
+Each LED may support the following indicator types:
+
+	==============	=======================================================
+	Indicator type	Meaning
+	==============	=======================================================
+	Power State	Shows if the device is powered and what power level
+			it is (e. g. if the device is suspended or not, and
+			on which kind of suspended level).
+	HDD Activity	Indicates if the LED is measuring the hard disk (or
+			SDD) activity.
+	Ethernet	Indicates the activity Ethernet adapter(s)
+	WiFi		Indicates if WiFi is enabled
+	Software	Doesn't indicate any hardware level. Instead, the LED
+			status is controlled via software.
+	Power Limit	Changes the LED color when the computer is throttling
+			its power limits.
+	Disable		The LED was disabled.
+	==============	=======================================================
+
+In order to change the type of indicator, you should
+just write a new value to the indicator type::
+
+    # echo "wifi" > /sys/class/leds/nuc::front1/indicator
+
+    $ cat /sys/class/leds/nuc::front1/indicator
+    Power State  HDD Activity  Ethernet  [WiFi]  Software  Power Limit  Disable
+
+
+Power State parameters
+----------------------
+
+When the LED indicator is measuring *Power State*, the following parameters
+may be available:
+
+    =================================	=======================================
+    Parameter				Meaning
+    =================================	=======================================
+    <power_state>_brightness		Brightness in percent (from 0 to 100)
+    <power_state>_blink_behavior	type of blink.
+					See :ref:`nuc_blink_behavior`.
+    <power_state>_blink_frequency	Blink frequency.
+					See :ref:`nuc_blink_behavior`.
+    <power_state>_color			LED color
+					See :ref:`nuc_color`.
+    =================================	=======================================
+
+Where <power_state> can be:
+
+On NUC8/9 API:
+
+    +------------+
+    | S0	 |
+    +------------+
+    | S3	 |
+    +------------+
+    | S5	 |
+    +------------+
+    | Ready mode |
+    +------------+
+
+On NUC10 API:
+
+    +------------+
+    | S0	 |
+    +------------+
+    | S3	 |
+    +------------+
+    | Standby	 |
+    +------------+
+
+HDD Activity parameters
+-----------------------
+
+When the LED indicator is measuring *HDD Activity*, the following parameters
+may be available:
+
+    =================================	=======================================
+    Parameter				Meaning
+    =================================	=======================================
+    brightness				Brightness in percent (from 0 to 100)
+    color				LED color.
+					See :ref:`nuc_color`.
+    hdd_default				Default is LED turned ON or OFF.
+					When set toOFF, the LED will turn on
+					at disk activity.
+					When set to ON, the LED will be turned
+					on by default, turning off at disk
+					activity.
+    =================================	=======================================
+
+Ethernet parameters
+-------------------
+
+When the LED indicator is measuring *Ethernet*, the following parameters
+may be available:
+
+    =================================	=======================================
+    Parameter				Meaning
+    =================================	=======================================
+    brightness				Brightness in percent (from 0 to 100)
+    color				LED color.
+					See :ref:`nuc_color`.
+    ethernet_type			What Ethernet interface is monitored.
+					Can be:
+					LAN1, LAN2 or LAN1+LAN2.
+    =================================	=======================================
+
+Power limit parameters
+----------------------
+
+When the LED indicator is measuring *Power limit*, the following parameters
+may be available:
+
+    =================================	=======================================
+    Parameter				Meaning
+    =================================	=======================================
+    brightness				Brightness in percent (from 0 to 100)
+    color				LED color.
+					See :ref:`nuc_color`.
+    power_limit_scheme			Indication scheme can be either:
+					- green to red
+					- single color
+    =================================	=======================================
+
+
+.. _nuc_color:
+
+NUC LED colors
+--------------
+
+The NUC LED API may support 3 types of LEDs:
+
+- Mono-colored LEDs;
+- Dual-colored LEDs;
+- multi-colored LEDs (only on NUC6/7);
+- RGB LEDs.
+
+Also, when a let is set to be a *Power limit* indicator, despite the
+physical device's LED color, the API may limit it to be a led that
+can display only green and red, or just a single color.
+
+The ``color`` and ``<power_state>_color`` parameter supports all those
+different settings.
+
+The color parameter can be set to those values:
+
+    ============	======	=====	=====
+    Color name		Red	Green	Blue
+    ============	======	=====	=====
+    blue		0	0	255
+    amber		255	191	0
+    white		255	255	255
+    red			255	0	0
+    green		0	255	0
+    yellow		255	255	0
+    cyan		0	255	255
+    magenta		255	0	255
+    <r>,<g>,<b>		<r>	<g>	<b>
+    ============	======	=====	=====
+
+The color parameter will refuse to set a LED on a color that it is not
+supported by the hardware or when the setting is incompatible with the
+indicator type. So, when the indicator is set to *Power limit*, and
+the  ``power_limit_scheme`` is set to ``green to red``, it doesn't
+let to set the LED's color.
+
+On the other hand, the behavior is identical if a color is written using
+the color's name or its RGB value.
+
+So::
+
+   $ cat /sys/class/leds/nuc::front1/color
+   red
+   # echo "green" > /sys/class/leds/nuc::front1/color
+   $ cat /sys/class/leds/nuc::front1/color
+   green
+   # echo "255,0,0" > /sys/class/leds/nuc::front1/color
+   $ cat /sys/class/leds/nuc::front1/color
+   red
+
+.. _nuc_blink_behavior:
+
+NUC Blink behavior
+------------------
+
+The NUC LEDs hardware supports the following types of blink behavior:
+
+    +------------+
+    | Solid      |
+    +------------+
+    | Breathing  |
+    +------------+
+    | Pulsing    |
+    +------------+
+    | Strobing   |
+    +------------+
+
+Changing the blink behavior will change how the led will be turning
+on and off when blinking. Setting it to ``Solid`` disables blinking.
+
+Please notice that not all types of indicator supports blinking.
+
+When blinking, the blink frequency can be changed via ``blink_frequency``
+or ``<power_state>_blink_frequency``, depending on the indicator.
+
+Setting it allows to change the blink frequency in Hz, ranging from 0.1 Hz
+to 1.0 Hz, in multiples of 0.1 Hz.
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH v2 02/17] leds: add support for NUC WMI LEDs
  2021-05-18 15:08 [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC Mauro Carvalho Chehab
  2021-05-18 15:08 ` [PATCH v2 01/17] docs: describe the API used to set NUC LEDs Mauro Carvalho Chehab
@ 2021-05-18 15:08 ` Mauro Carvalho Chehab
  2021-05-18 15:08 ` [PATCH v2 03/17] leds: leds-nuc: detect WMI API detection Mauro Carvalho Chehab
                   ` (15 subsequent siblings)
  17 siblings, 0 replies; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-18 15:08 UTC (permalink / raw)
  Cc: linuxarm, mauro.chehab, Mauro Carvalho Chehab, Pavel Machek,
	gregkh, linux-kernel, linux-leds

Some 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.

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
 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

diff --git a/MAINTAINERS b/MAINTAINERS
index bd7aff0c120f..316f0e552ca6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -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 <mchehab@kernel.org>
+L:	linux-leds@vger.kernel.org
+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 <fthain@telegraphics.com.au>
 L:	linux-m68k@lists.linux-m68k.org
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 49d99cb084db..f5b7f7a02df5 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -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
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 7e604d3028c8..11a4d29bf9a0 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -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
diff --git a/drivers/leds/leds-nuc.c b/drivers/leds/leds-nuc.c
new file mode 100644
index 000000000000..69bab319122e
--- /dev/null
+++ b/drivers/leds/leds-nuc.c
@@ -0,0 +1,481 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Intel NUC WMI Control WMI Driver
+ *
+ * Currently, it implements only the LED support
+ *
+ * Copyright(c) 2021 Mauro Carvalho Chehab
+ *
+ * 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
+ */
+
+#include <linux/acpi.h>
+#include <linux/bits.h>
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/wmi.h>
+
+#define NUC_LED_WMI_GUID	"8C5DA44C-CDC3-46B3-8619-4E26D34390B7"
+
+#define MAX_LEDS		7
+#define NUM_INPUT_ARGS		4
+#define NUM_OUTPUT_ARGS		3
+
+enum led_cmds {
+	LED_QUERY			= 0x03,
+	LED_NEW_GET_STATUS		= 0x04,
+	LED_SET_INDICATOR		= 0x05,
+	LED_SET_VALUE			= 0x06,
+	LED_NOTIFICATION		= 0x07,
+	LED_SWITCH_TYPE			= 0x08,
+};
+
+enum led_query_subcmd {
+	LED_QUERY_LIST_ALL		= 0x00,
+	LED_QUERY_COLOR_TYPE		= 0x01,
+	LED_QUERY_INDICATOR_OPTIONS	= 0x02,
+	LED_QUERY_CONTROL_ITEMS		= 0x03,
+};
+
+enum led_new_get_subcmd {
+	LED_NEW_GET_CURRENT_INDICATOR	= 0x00,
+	LED_NEW_GET_CONTROL_ITEM	= 0x01,
+};
+
+/* 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",
+	"nuc::skull",
+	"nuc::eyes",
+	"nuc::front1",
+	"nuc::front2",
+	"nuc::front3",
+};
+
+struct nuc_nmi_led {
+	struct led_classdev cdev;
+	struct device *dev;
+	u8 id;
+	u8 indicator;
+	u32 color_type;
+	u32 avail_indicators;
+	u32 control_items;
+};
+
+struct nuc_wmi {
+	struct nuc_nmi_led led[MAX_LEDS * 3];	/* Worse case: RGB LEDs */
+	int num_leds;
+
+	/* Avoid concurrent access to WMI */
+	struct mutex wmi_lock;
+};
+
+static int nuc_nmi_led_error(u8 error_code)
+{
+	switch (error_code) {
+	case 0:
+		return 0;
+	case 0xe1:	/* Function not support */
+		return -ENOENT;
+	case 0xe2:	/* Undefined device */
+		return -ENODEV;
+	case 0xe3:	/* EC no respond */
+		return -EIO;
+	case 0xe4:	/* Invalid Parameter */
+		return -EINVAL;
+	case 0xef:	/* Unexpected error */
+		return -EFAULT;
+
+	/* Revision 1.0 Errors */
+	case 0xe5:	/* Node busy */
+		return -EBUSY;
+	case 0xe6:	/* Destination device is disabled or unavailable */
+		return -EACCES;
+	case 0xe7:	/* Invalid CEC Opcode */
+		return -ENOENT;
+	case 0xe8:	/* Data Buffer size is not enough */
+		return -ENOSPC;
+
+	default:	/* Reserved */
+		return -EPROTO;
+	}
+}
+
+static int nuc_nmi_cmd(struct device *dev,
+		       u8 cmd,
+		       u8 input_args[NUM_INPUT_ARGS],
+		       u8 output_args[NUM_OUTPUT_ARGS])
+{
+	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+	struct nuc_wmi *priv = dev_get_drvdata(dev);
+	struct acpi_buffer input;
+	union acpi_object *obj;
+	acpi_status status;
+	int size, ret;
+	u8 *p;
+
+	input.length = NUM_INPUT_ARGS;
+	input.pointer = input_args;
+
+	mutex_lock(&priv->wmi_lock);
+	status = wmi_evaluate_method(NUC_LED_WMI_GUID, 0, cmd,
+				     &input, &output);
+	mutex_unlock(&priv->wmi_lock);
+	if (ACPI_FAILURE(status)) {
+		dev_warn(dev, "cmd %02x (%*ph): ACPI failure: %d\n",
+			 cmd, (int)input.length, input_args, ret);
+		return status;
+	}
+
+	obj = output.pointer;
+	if (!obj) {
+		dev_warn(dev, "cmd %02x (%*ph): no output\n",
+			 cmd, (int)input.length, input_args);
+		return -EINVAL;
+	}
+
+	if (obj->type == ACPI_TYPE_BUFFER) {
+		if (obj->buffer.length < NUM_OUTPUT_ARGS + 1) {
+			ret = -EINVAL;
+			goto err;
+		}
+		p = (u8 *)obj->buffer.pointer;
+	} else if (obj->type == ACPI_TYPE_INTEGER) {
+		p = (u8 *)&obj->integer.value;
+	} else {
+		return -EINVAL;
+	}
+
+	ret = nuc_nmi_led_error(p[0]);
+	if (ret) {
+		dev_warn(dev, "cmd %02x (%*ph): WMI error code: %02x\n",
+			 cmd, (int)input.length, input_args, p[0]);
+		goto err;
+	}
+
+	size = NUM_OUTPUT_ARGS + 1;
+
+	if (output_args) {
+		memcpy(output_args, p + 1, NUM_OUTPUT_ARGS);
+
+		dev_info(dev, "cmd %02x (%*ph), return: %*ph\n",
+			 cmd, (int)input.length, input_args, NUM_OUTPUT_ARGS, output_args);
+	} else {
+		dev_info(dev, "cmd %02x (%*ph)\n",
+			 cmd, (int)input.length, input_args);
+	}
+
+err:
+	kfree(obj);
+	return ret;
+}
+
+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;
+	u8 leds;
+
+	/*
+	 * 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.
+	 */
+	cmd = LED_QUERY;
+	input[0] = LED_QUERY_LIST_ALL;
+	ret = nuc_nmi_cmd(dev, cmd, input, output);
+	if (ret) {
+		dev_warn(dev, "error %d while listing all LEDs\n", ret);
+		return ret;
+	}
+
+	leds = output[0];
+	if (!leds) {
+		dev_warn(dev, "No LEDs found\n");
+		return -ENODEV;
+	}
+
+	for (id = 0; id < MAX_LEDS; id++) {
+		struct nuc_nmi_led *led = &priv->led[priv->num_leds];
+
+		if (!(leds & BIT(id)))
+			continue;
+
+		led->id = id;
+
+		cmd = LED_QUERY;
+		input[0] = LED_QUERY_COLOR_TYPE;
+		input[1] = id;
+		ret = nuc_nmi_cmd(dev, cmd, input, output);
+		if (ret) {
+			dev_warn(dev, "error %d on led %i\n", ret, i);
+			return ret;
+		}
+
+		led->color_type = output[0]      |
+				  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);
+		if (ret) {
+			dev_warn(dev, "error %d on led %i\n", ret, i);
+			return ret;
+		}
+
+		led->avail_indicators = output[0]      |
+					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);
+		if (ret) {
+			dev_warn(dev, "error %d on led %i\n", ret, i);
+			return ret;
+		}
+
+		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);
+
+		priv->num_leds++;
+	}
+
+	return 0;
+}
+
+/*
+ * LED show/store routines
+ */
+
+#define LED_ATTR_RW(_name) \
+	DEVICE_ATTR(_name, 0644, show_##_name, store_##_name)
+
+static const char * const led_indicators[] = {
+	"Power State",
+	"HDD Activity",
+	"Ethernet",
+	"WiFi",
+	"Software",
+	"Power Limit",
+	"Disable"
+};
+
+static ssize_t show_indicator(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 size = PAGE_SIZE;
+	char *p = buf;
+	int i, n;
+
+	for (i = 0; i < fls(led->avail_indicators); i++) {
+		if (!(led->avail_indicators & BIT(i)))
+			continue;
+		if (i == led->indicator)
+			n = scnprintf(p, size, "[%s]  ", led_indicators[i]);
+		else
+			n = scnprintf(p, size, "%s  ", led_indicators[i]);
+		p += n;
+		size -= n;
+	}
+	size -= scnprintf(p, size, "\n");
+
+	return PAGE_SIZE - size;
+}
+
+static ssize_t store_indicator(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 };
+	const char *tmp;
+	int ret, i;
+
+	tmp = strsep((char **)&buf, "\n");
+
+	for (i = 0; i < fls(led->avail_indicators); i++) {
+		if (!(led->avail_indicators & BIT(i)))
+			continue;
+
+		if (!strcasecmp(tmp, led_indicators[i])) {
+			cmd = LED_SET_INDICATOR;
+			input[0] = led->id;
+			input[1] = i;
+
+			dev_dbg(dev, "set led %s indicator to %s\n",
+				cdev->name, led_indicators[i]);
+
+			ret = nuc_nmi_cmd(dev, cmd, input, NULL);
+			if (ret)
+				return ret;
+
+			led->indicator = i;
+
+			return len;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static LED_ATTR_RW(indicator);
+
+/*
+ * Attributes for multicolor LEDs
+ */
+
+static struct attribute *nuc_wmi_multicolor_led_attr[] = {
+	&dev_attr_indicator.attr,
+	NULL,
+};
+
+static const struct attribute_group nuc_wmi_led_attribute_group = {
+	.attrs		= nuc_wmi_multicolor_led_attr,
+};
+
+static const struct attribute_group *nuc_wmi_led_attribute_groups[] = {
+	&nuc_wmi_led_attribute_group,
+	NULL
+};
+
+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->cdev.groups = nuc_wmi_led_attribute_groups;
+
+	/*
+	 * 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.
+	 */
+
+	return devm_led_classdev_register(dev, &led->cdev);
+}
+
+static int nuc_wmi_leds_setup(struct device *dev)
+{
+	struct nuc_wmi *priv = dev_get_drvdata(dev);
+	int ret, i;
+
+	ret = nuc_wmi_query_leds(dev);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < priv->num_leds; i++) {
+		ret = nuc_wmi_led_register(dev, &priv->led[i]);
+		if (ret) {
+			dev_err(dev, "Failed to register led %d: %s\n",
+				i, led_names[priv->led[i].id]);
+			while (--i >= 0)
+				devm_led_classdev_unregister(dev, &priv->led[i].cdev);
+
+			return ret;
+		}
+	}
+	return 0;
+}
+
+static int nuc_wmi_probe(struct wmi_device *wdev, const void *context)
+{
+	struct device *dev = &wdev->dev;
+	struct nuc_wmi *priv;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	mutex_init(&priv->wmi_lock);
+
+	dev_set_drvdata(dev, priv);
+
+	ret = nuc_wmi_leds_setup(dev);
+	if (ret)
+		return ret;
+
+	dev_info(dev, "NUC WMI driver initialized.\n");
+	return 0;
+}
+
+static const struct wmi_device_id nuc_wmi_descriptor_id_table[] = {
+	{ .guid_string = NUC_LED_WMI_GUID },
+	{ },
+};
+
+static struct wmi_driver nuc_wmi_driver = {
+	.driver = {
+		.name = "nuc-wmi",
+	},
+	.probe = nuc_wmi_probe,
+	.id_table = nuc_wmi_descriptor_id_table,
+};
+
+module_wmi_driver(nuc_wmi_driver);
+
+MODULE_DEVICE_TABLE(wmi, nuc_wmi_descriptor_id_table);
+MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab+huawei@kernel.org>");
+MODULE_DESCRIPTION("Intel NUC WMI LED driver");
+MODULE_LICENSE("GPL");
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH v2 03/17] leds: leds-nuc: detect WMI API detection
  2021-05-18 15:08 [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC Mauro Carvalho Chehab
  2021-05-18 15:08 ` [PATCH v2 01/17] docs: describe the API used to set NUC LEDs Mauro Carvalho Chehab
  2021-05-18 15:08 ` [PATCH v2 02/17] leds: add support for NUC WMI LEDs Mauro Carvalho Chehab
@ 2021-05-18 15:08 ` Mauro Carvalho Chehab
  2021-05-18 15:08 ` [PATCH v2 04/17] leds: leds-nuc: add support for changing S0 brightness Mauro Carvalho Chehab
                   ` (14 subsequent siblings)
  17 siblings, 0 replies; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-18 15:08 UTC (permalink / raw)
  Cc: linuxarm, mauro.chehab, Mauro Carvalho Chehab, Pavel Machek,
	gregkh, linux-kernel, linux-leds

There 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.

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
 drivers/leds/leds-nuc.c | 33 ++++++++++++++++++++++++++++++---
 1 file changed, 30 insertions(+), 3 deletions(-)

diff --git a/drivers/leds/leds-nuc.c b/drivers/leds/leds-nuc.c
index 69bab319122e..26bc4a4bb57c 100644
--- a/drivers/leds/leds-nuc.c
+++ b/drivers/leds/leds-nuc.c
@@ -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;
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH v2 04/17] leds: leds-nuc: add support for changing S0 brightness
  2021-05-18 15:08 [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC Mauro Carvalho Chehab
                   ` (2 preceding siblings ...)
  2021-05-18 15:08 ` [PATCH v2 03/17] leds: leds-nuc: detect WMI API detection Mauro Carvalho Chehab
@ 2021-05-18 15:08 ` Mauro Carvalho Chehab
  2021-05-18 15:08 ` [PATCH v2 05/17] leds: leds-nuc: add all types of brightness Mauro Carvalho Chehab
                   ` (13 subsequent siblings)
  17 siblings, 0 replies; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-18 15:08 UTC (permalink / raw)
  Cc: linuxarm, mauro.chehab, Mauro Carvalho Chehab, Pavel Machek,
	gregkh, linux-kernel, linux-leds

Now that the core logic is in place, let's add support to
adjust the S0 brightness level.

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
 drivers/leds/leds-nuc.c | 79 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 79 insertions(+)

diff --git a/drivers/leds/leds-nuc.c b/drivers/leds/leds-nuc.c
index 26bc4a4bb57c..e12fa2e7a488 100644
--- a/drivers/leds/leds-nuc.c
+++ b/drivers/leds/leds-nuc.c
@@ -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,
 };
 
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH v2 05/17] leds: leds-nuc: add all types of brightness
  2021-05-18 15:08 [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC Mauro Carvalho Chehab
                   ` (3 preceding siblings ...)
  2021-05-18 15:08 ` [PATCH v2 04/17] leds: leds-nuc: add support for changing S0 brightness Mauro Carvalho Chehab
@ 2021-05-18 15:08 ` Mauro Carvalho Chehab
  2021-05-18 15:08 ` [PATCH v2 06/17] leds: leds-nuc: allow changing the LED colors Mauro Carvalho Chehab
                   ` (12 subsequent siblings)
  17 siblings, 0 replies; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-18 15:08 UTC (permalink / raw)
  Cc: linuxarm, mauro.chehab, Mauro Carvalho Chehab, Pavel Machek,
	gregkh, linux-kernel, linux-leds

Improve the logic in order to support not only S0 brightness,
but also the brightness for other indicators and for all
power states.

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
 drivers/leds/leds-nuc.c | 369 +++++++++++++++++++++++++++-------------
 1 file changed, 249 insertions(+), 120 deletions(-)

diff --git a/drivers/leds/leds-nuc.c b/drivers/leds/leds-nuc.c
index e12fa2e7a488..df65bf17e0e6 100644
--- a/drivers/leds/leds-nuc.c
+++ b/drivers/leds/leds-nuc.c
@@ -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);
 }
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH v2 06/17] leds: leds-nuc: allow changing the LED colors
  2021-05-18 15:08 [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC Mauro Carvalho Chehab
                   ` (4 preceding siblings ...)
  2021-05-18 15:08 ` [PATCH v2 05/17] leds: leds-nuc: add all types of brightness Mauro Carvalho Chehab
@ 2021-05-18 15:08 ` Mauro Carvalho Chehab
  2021-05-18 15:08 ` [PATCH v2 07/17] leds: leds-nuc: add support for WMI API version 1.0 Mauro Carvalho Chehab
                   ` (11 subsequent siblings)
  17 siblings, 0 replies; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-18 15:08 UTC (permalink / raw)
  Cc: linuxarm, mauro.chehab, Mauro Carvalho Chehab, Pavel Machek,
	gregkh, linux-kernel, linux-leds

Add routines to allow seeing and changing the LED colors.

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
 drivers/leds/leds-nuc.c | 244 +++++++++++++++++++++++++++++++++++++---
 1 file changed, 228 insertions(+), 16 deletions(-)

diff --git a/drivers/leds/leds-nuc.c b/drivers/leds/leds-nuc.c
index df65bf17e0e6..415031d344c6 100644
--- a/drivers/leds/leds-nuc.c
+++ b/drivers/leds/leds-nuc.c
@@ -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
 };
 
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH v2 07/17] leds: leds-nuc: add support for WMI API version 1.0
  2021-05-18 15:08 [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC Mauro Carvalho Chehab
                   ` (5 preceding siblings ...)
  2021-05-18 15:08 ` [PATCH v2 06/17] leds: leds-nuc: allow changing the LED colors Mauro Carvalho Chehab
@ 2021-05-18 15:08 ` Mauro Carvalho Chehab
  2021-05-18 15:08 ` [PATCH v2 08/17] leds: leds-nuc: add basic support for NUC6 WMI Mauro Carvalho Chehab
                   ` (10 subsequent siblings)
  17 siblings, 0 replies; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-18 15:08 UTC (permalink / raw)
  Cc: linuxarm, mauro.chehab, Mauro Carvalho Chehab, Pavel Machek,
	gregkh, linux-kernel, linux-leds

The 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.

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
 drivers/leds/leds-nuc.c | 119 +++++++++++++++++++++++++++++++++-------
 1 file changed, 99 insertions(+), 20 deletions(-)

diff --git a/drivers/leds/leds-nuc.c b/drivers/leds/leds-nuc.c
index 415031d344c6..e0090626aeec 100644
--- a/drivers/leds/leds-nuc.c
+++ b/drivers/leds/leds-nuc.c
@@ -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]);
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH v2 08/17] leds: leds-nuc: add basic support for NUC6 WMI
  2021-05-18 15:08 [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC Mauro Carvalho Chehab
                   ` (6 preceding siblings ...)
  2021-05-18 15:08 ` [PATCH v2 07/17] leds: leds-nuc: add support for WMI API version 1.0 Mauro Carvalho Chehab
@ 2021-05-18 15:08 ` Mauro Carvalho Chehab
  2021-05-18 15:08 ` [PATCH v2 09/17] leds: leds-nuc: add brightness and color for NUC6 API Mauro Carvalho Chehab
                   ` (9 subsequent siblings)
  17 siblings, 0 replies; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-18 15:08 UTC (permalink / raw)
  Cc: linuxarm, mauro.chehab, Mauro Carvalho Chehab, Pavel Machek,
	gregkh, linux-kernel, linux-leds

The 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/

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
 drivers/leds/leds-nuc.c | 142 +++++++++++++++++++++++++++++++---------
 1 file changed, 110 insertions(+), 32 deletions(-)

diff --git a/drivers/leds/leds-nuc.c b/drivers/leds/leds-nuc.c
index e0090626aeec..cd15ed824234 100644
--- a/drivers/leds/leds-nuc.c
+++ b/drivers/leds/leds-nuc.c
@@ -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)
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH v2 09/17] leds: leds-nuc: add brightness and color for NUC6 API
  2021-05-18 15:08 [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC Mauro Carvalho Chehab
                   ` (7 preceding siblings ...)
  2021-05-18 15:08 ` [PATCH v2 08/17] leds: leds-nuc: add basic support for NUC6 WMI Mauro Carvalho Chehab
@ 2021-05-18 15:08 ` Mauro Carvalho Chehab
  2021-05-18 15:08 ` [PATCH v2 10/17] leds: leds-nuc: Add support to blink behavior for NUC8/10 Mauro Carvalho Chehab
                   ` (8 subsequent siblings)
  17 siblings, 0 replies; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-18 15:08 UTC (permalink / raw)
  Cc: linuxarm, mauro.chehab, Mauro Carvalho Chehab, Pavel Machek,
	gregkh, linux-kernel, linux-leds

The 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.

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
 drivers/leds/leds-nuc.c | 198 ++++++++++++++++++++++++++++++++++++++--
 1 file changed, 191 insertions(+), 7 deletions(-)

diff --git a/drivers/leds/leds-nuc.c b/drivers/leds/leds-nuc.c
index cd15ed824234..03fa8bafc5de 100644
--- a/drivers/leds/leds-nuc.c
+++ b/drivers/leds/leds-nuc.c
@@ -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);
 	}
 
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH v2 10/17] leds: leds-nuc: Add support to blink behavior for NUC8/10
  2021-05-18 15:08 [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC Mauro Carvalho Chehab
                   ` (8 preceding siblings ...)
  2021-05-18 15:08 ` [PATCH v2 09/17] leds: leds-nuc: add brightness and color for NUC6 API Mauro Carvalho Chehab
@ 2021-05-18 15:08 ` Mauro Carvalho Chehab
  2021-05-19  7:58   ` Marek Behun
  2021-05-18 15:09 ` [PATCH v2 11/17] leds: leds-nuc: get rid of an unused variable Mauro Carvalho Chehab
                   ` (7 subsequent siblings)
  17 siblings, 1 reply; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-18 15:08 UTC (permalink / raw)
  Cc: linuxarm, mauro.chehab, Mauro Carvalho Chehab, Pavel Machek,
	gregkh, linux-kernel, linux-leds

The 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.

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
 drivers/leds/leds-nuc.c | 347 +++++++++++++++++++++++++++++++++++++---
 1 file changed, 322 insertions(+), 25 deletions(-)

diff --git a/drivers/leds/leds-nuc.c b/drivers/leds/leds-nuc.c
index 03fa8bafc5de..a5eb625d7b51 100644
--- a/drivers/leds/leds-nuc.c
+++ b/drivers/leds/leds-nuc.c
@@ -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
 };
 
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH v2 11/17] leds: leds-nuc: get rid of an unused variable
  2021-05-18 15:08 [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC Mauro Carvalho Chehab
                   ` (9 preceding siblings ...)
  2021-05-18 15:08 ` [PATCH v2 10/17] leds: leds-nuc: Add support to blink behavior for NUC8/10 Mauro Carvalho Chehab
@ 2021-05-18 15:09 ` Mauro Carvalho Chehab
  2021-05-18 15:09 ` [PATCH v2 12/17] leds: leds-nuc: implement blink control for NUC6 Mauro Carvalho Chehab
                   ` (6 subsequent siblings)
  17 siblings, 0 replies; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-18 15:09 UTC (permalink / raw)
  Cc: linuxarm, mauro.chehab, Mauro Carvalho Chehab, Pavel Machek,
	gregkh, linux-kernel, linux-leds

	drivers/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;
	      |      ^~~~

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
 drivers/leds/leds-nuc.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/drivers/leds/leds-nuc.c b/drivers/leds/leds-nuc.c
index a5eb625d7b51..e2517e1a367f 100644
--- a/drivers/leds/leds-nuc.c
+++ b/drivers/leds/leds-nuc.c
@@ -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);
 
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH v2 12/17] leds: leds-nuc: implement blink control for NUC6
  2021-05-18 15:08 [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC Mauro Carvalho Chehab
                   ` (10 preceding siblings ...)
  2021-05-18 15:09 ` [PATCH v2 11/17] leds: leds-nuc: get rid of an unused variable Mauro Carvalho Chehab
@ 2021-05-18 15:09 ` Mauro Carvalho Chehab
  2021-05-18 15:09 ` [PATCH v2 13/17] leds: leds-nuc: better detect NUC6/NUC7 devices Mauro Carvalho Chehab
                   ` (5 subsequent siblings)
  17 siblings, 0 replies; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-18 15:09 UTC (permalink / raw)
  Cc: linuxarm, mauro.chehab, Mauro Carvalho Chehab, Pavel Machek,
	gregkh, linux-kernel, linux-leds

The 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.

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
 drivers/leds/leds-nuc.c | 269 +++++++++++++++++++++++++++++++++++++++-
 1 file changed, 267 insertions(+), 2 deletions(-)

diff --git a/drivers/leds/leds-nuc.c b/drivers/leds/leds-nuc.c
index e2517e1a367f..6aa0bf16c8b7 100644
--- a/drivers/leds/leds-nuc.c
+++ b/drivers/leds/leds-nuc.c
@@ -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
 };
 
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH v2 13/17] leds: leds-nuc: better detect NUC6/NUC7 devices
  2021-05-18 15:08 [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC Mauro Carvalho Chehab
                   ` (11 preceding siblings ...)
  2021-05-18 15:09 ` [PATCH v2 12/17] leds: leds-nuc: implement blink control for NUC6 Mauro Carvalho Chehab
@ 2021-05-18 15:09 ` Mauro Carvalho Chehab
  2021-05-18 15:09 ` [PATCH v2 14/17] leds: leds-nuc: add support for HDD activity default Mauro Carvalho Chehab
                   ` (4 subsequent siblings)
  17 siblings, 0 replies; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-18 15:09 UTC (permalink / raw)
  Cc: linuxarm, mauro.chehab, Mauro Carvalho Chehab, Pavel Machek,
	gregkh, linux-kernel, linux-leds

There'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.

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
 drivers/leds/leds-nuc.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/drivers/leds/leds-nuc.c b/drivers/leds/leds-nuc.c
index 6aa0bf16c8b7..af57f54cfb05 100644
--- a/drivers/leds/leds-nuc.c
+++ b/drivers/leds/leds-nuc.c
@@ -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;
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH v2 14/17] leds: leds-nuc: add support for HDD activity default
  2021-05-18 15:08 [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC Mauro Carvalho Chehab
                   ` (12 preceding siblings ...)
  2021-05-18 15:09 ` [PATCH v2 13/17] leds: leds-nuc: better detect NUC6/NUC7 devices Mauro Carvalho Chehab
@ 2021-05-18 15:09 ` Mauro Carvalho Chehab
  2021-05-18 15:09 ` [PATCH v2 15/17] leds: leds-nuc: fix software blink behavior logic Mauro Carvalho Chehab
                   ` (3 subsequent siblings)
  17 siblings, 0 replies; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-18 15:09 UTC (permalink / raw)
  Cc: linuxarm, mauro.chehab, Mauro Carvalho Chehab, Pavel Machek,
	gregkh, linux-kernel, linux-leds

There 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.

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
 drivers/leds/leds-nuc.c | 77 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 77 insertions(+)

diff --git a/drivers/leds/leds-nuc.c b/drivers/leds/leds-nuc.c
index af57f54cfb05..719a57841c03 100644
--- a/drivers/leds/leds-nuc.c
+++ b/drivers/leds/leds-nuc.c
@@ -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,
 };
 
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH v2 15/17] leds: leds-nuc: fix software blink behavior logic
  2021-05-18 15:08 [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC Mauro Carvalho Chehab
                   ` (13 preceding siblings ...)
  2021-05-18 15:09 ` [PATCH v2 14/17] leds: leds-nuc: add support for HDD activity default Mauro Carvalho Chehab
@ 2021-05-18 15:09 ` Mauro Carvalho Chehab
  2021-05-18 15:09 ` [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator Mauro Carvalho Chehab
                   ` (2 subsequent siblings)
  17 siblings, 0 replies; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-18 15:09 UTC (permalink / raw)
  Cc: linuxarm, mauro.chehab, Mauro Carvalho Chehab, Pavel Machek,
	gregkh, linux-kernel, linux-leds

The 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.

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
 drivers/leds/leds-nuc.c | 30 ++++++++++++------------------
 1 file changed, 12 insertions(+), 18 deletions(-)

diff --git a/drivers/leds/leds-nuc.c b/drivers/leds/leds-nuc.c
index 719a57841c03..4d4ea6fbeff4 100644
--- a/drivers/leds/leds-nuc.c
+++ b/drivers/leds/leds-nuc.c
@@ -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,
 };
 
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
  2021-05-18 15:08 [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC Mauro Carvalho Chehab
                   ` (14 preceding siblings ...)
  2021-05-18 15:09 ` [PATCH v2 15/17] leds: leds-nuc: fix software blink behavior logic Mauro Carvalho Chehab
@ 2021-05-18 15:09 ` Mauro Carvalho Chehab
  2021-05-19  8:02   ` Marek Behún
  2021-05-18 15:09 ` [PATCH v2 17/17] leds: leds-nuc: add support for changing the power limit scheme Mauro Carvalho Chehab
  2021-05-19 11:11 ` [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC Pavel Machek
  17 siblings, 1 reply; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-18 15:09 UTC (permalink / raw)
  Cc: linuxarm, mauro.chehab, Mauro Carvalho Chehab, Pavel Machek,
	gregkh, linux-kernel, linux-leds

The Ethernet type indicator can be configured to show the status
of LAN1, LAN1 or both. Add support for it.

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
 drivers/leds/leds-nuc.c | 89 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 89 insertions(+)

diff --git a/drivers/leds/leds-nuc.c b/drivers/leds/leds-nuc.c
index 4d4ea6fbeff4..f84ec5662f5c 100644
--- a/drivers/leds/leds-nuc.c
+++ b/drivers/leds/leds-nuc.c
@@ -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,
 };
 
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* [PATCH v2 17/17] leds: leds-nuc: add support for changing the power limit scheme
  2021-05-18 15:08 [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC Mauro Carvalho Chehab
                   ` (15 preceding siblings ...)
  2021-05-18 15:09 ` [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator Mauro Carvalho Chehab
@ 2021-05-18 15:09 ` Mauro Carvalho Chehab
  2021-05-19 11:11 ` [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC Pavel Machek
  17 siblings, 0 replies; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-18 15:09 UTC (permalink / raw)
  Cc: linuxarm, mauro.chehab, Mauro Carvalho Chehab, Pavel Machek,
	gregkh, linux-kernel, linux-leds

The 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.

Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
 drivers/leds/leds-nuc.c | 93 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 93 insertions(+)

diff --git a/drivers/leds/leds-nuc.c b/drivers/leds/leds-nuc.c
index f84ec5662f5c..c320a7e4c796 100644
--- a/drivers/leds/leds-nuc.c
+++ b/drivers/leds/leds-nuc.c
@@ -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,
 };
 
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 10/17] leds: leds-nuc: Add support to blink behavior for NUC8/10
  2021-05-18 15:08 ` [PATCH v2 10/17] leds: leds-nuc: Add support to blink behavior for NUC8/10 Mauro Carvalho Chehab
@ 2021-05-19  7:58   ` Marek Behun
  2021-05-19 10:09     ` Mauro Carvalho Chehab
  0 siblings, 1 reply; 44+ messages in thread
From: Marek Behun @ 2021-05-19  7:58 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: linuxarm, mauro.chehab, Pavel Machek, gregkh, linux-kernel, linux-leds

This should be implemented via the blink trigger or hw_pattern, I think.
Have you looked at these?

Marek

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
  2021-05-18 15:09 ` [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator Mauro Carvalho Chehab
@ 2021-05-19  8:02   ` Marek Behún
  2021-05-19 10:18     ` Mauro Carvalho Chehab
  0 siblings, 1 reply; 44+ messages in thread
From: Marek Behún @ 2021-05-19  8:02 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: linuxarm, mauro.chehab, Pavel Machek, gregkh, linux-kernel, linux-leds

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

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 10/17] leds: leds-nuc: Add support to blink behavior for NUC8/10
  2021-05-19  7:58   ` Marek Behun
@ 2021-05-19 10:09     ` Mauro Carvalho Chehab
  0 siblings, 0 replies; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-19 10:09 UTC (permalink / raw)
  To: Marek Behun
  Cc: linuxarm, mauro.chehab, Pavel Machek, gregkh, linux-kernel, linux-leds

Em Wed, 19 May 2021 09:58:50 +0200
Marek Behun <marek.behun@nic.cz> escreveu:

> 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

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
  2021-05-19  8:02   ` Marek Behún
@ 2021-05-19 10:18     ` Mauro Carvalho Chehab
  2021-05-19 12:11       ` Marek Behún
  0 siblings, 1 reply; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-19 10:18 UTC (permalink / raw)
  To: Marek Behún
  Cc: linuxarm, mauro.chehab, Pavel Machek, gregkh, linux-kernel, linux-leds

Em Wed, 19 May 2021 10:02:53 +0200
Marek Behún <kabel@kernel.org> escreveu:

> 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.

> 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).
 
> 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

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC
  2021-05-18 15:08 [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC Mauro Carvalho Chehab
                   ` (16 preceding siblings ...)
  2021-05-18 15:09 ` [PATCH v2 17/17] leds: leds-nuc: add support for changing the power limit scheme Mauro Carvalho Chehab
@ 2021-05-19 11:11 ` Pavel Machek
  2021-05-19 12:15   ` Mauro Carvalho Chehab
  17 siblings, 1 reply; 44+ messages in thread
From: Pavel Machek @ 2021-05-19 11:11 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: linuxarm, mauro.chehab, gregkh, linux-doc, linux-kernel, linux-leds

[-- Attachment #1: Type: text/plain, Size: 755 bytes --]

Hi!

> 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
-- 
http://www.livejournal.com/~pavelmachek

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
  2021-05-19 10:18     ` Mauro Carvalho Chehab
@ 2021-05-19 12:11       ` Marek Behún
  2021-05-19 14:24         ` Mauro Carvalho Chehab
  0 siblings, 1 reply; 44+ messages in thread
From: Marek Behún @ 2021-05-19 12:11 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: linuxarm, mauro.chehab, Pavel Machek, gregkh, linux-kernel, linux-leds

On Wed, 19 May 2021 12:18:12 +0200
Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:

> Em Wed, 19 May 2021 10:02:53 +0200
> Marek Behún <kabel@kernel.org> escreveu:
> 
> > 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.
> 
> > 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).

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...

> > 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

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC
  2021-05-19 11:11 ` [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC Pavel Machek
@ 2021-05-19 12:15   ` Mauro Carvalho Chehab
  2021-05-19 19:41     ` Pavel Machek
  0 siblings, 1 reply; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-19 12:15 UTC (permalink / raw)
  To: Pavel Machek
  Cc: linuxarm, mauro.chehab, gregkh, linux-doc, linux-kernel, linux-leds

Em Wed, 19 May 2021 13:11:07 +0200
Pavel Machek <pavel@ucw.cz> escreveu:

> Hi!
> 
> > 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.

> 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.

> 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

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
  2021-05-19 12:11       ` Marek Behún
@ 2021-05-19 14:24         ` Mauro Carvalho Chehab
  2021-05-19 15:55           ` Marek Behún
  0 siblings, 1 reply; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-19 14:24 UTC (permalink / raw)
  To: Marek Behún
  Cc: linuxarm, mauro.chehab, Pavel Machek, gregkh, linux-kernel, linux-leds

Em Wed, 19 May 2021 14:11:02 +0200
Marek Behún <kabel@kernel.org> escreveu:

> On Wed, 19 May 2021 12:18:12 +0200
> Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:
> 
> > Em Wed, 19 May 2021 10:02:53 +0200
> > Marek Behún <kabel@kernel.org> escreveu:
> >   
> > > 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.
> >   
> > > 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).  
> 
> 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.

> 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

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
  2021-05-19 14:24         ` Mauro Carvalho Chehab
@ 2021-05-19 15:55           ` Marek Behún
  2021-05-19 18:30             ` Mauro Carvalho Chehab
  0 siblings, 1 reply; 44+ messages in thread
From: Marek Behún @ 2021-05-19 15:55 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: linuxarm, mauro.chehab, Pavel Machek, gregkh, linux-kernel, linux-leds

On Wed, 19 May 2021 16:24:13 +0200
Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:

> 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.

> 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.

> 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).

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.

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
  2021-05-19 15:55           ` Marek Behún
@ 2021-05-19 18:30             ` Mauro Carvalho Chehab
  2021-05-20 11:00               ` Marek Behún
  0 siblings, 1 reply; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-19 18:30 UTC (permalink / raw)
  To: Marek Behún
  Cc: linuxarm, mauro.chehab, Pavel Machek, gregkh, linux-kernel, linux-leds

Em Wed, 19 May 2021 17:55:03 +0200
Marek Behún <kabel@kernel.org> escreveu:

> On Wed, 19 May 2021 16:24:13 +0200
> Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:
> 
> > 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);

> 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.
> 
> > 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.
> 
> > 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).  
> 
> 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.

> 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.

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.

> 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.

> 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.

> 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.

> 
> Marek
> 
> PS: This is relevant for disk activity as well.



Thanks,
Mauro

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC
  2021-05-19 12:15   ` Mauro Carvalho Chehab
@ 2021-05-19 19:41     ` Pavel Machek
  2021-05-19 23:07       ` Mauro Carvalho Chehab
  0 siblings, 1 reply; 44+ messages in thread
From: Pavel Machek @ 2021-05-19 19:41 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: linuxarm, mauro.chehab, gregkh, linux-doc, linux-kernel, linux-leds

[-- Attachment #1: Type: text/plain, Size: 1708 bytes --]

Hi!

> > 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.

> > 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.

> 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.

> [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
-- 
http://www.livejournal.com/~pavelmachek

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 195 bytes --]

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC
  2021-05-19 19:41     ` Pavel Machek
@ 2021-05-19 23:07       ` Mauro Carvalho Chehab
  2021-05-20 16:19         ` Marek Behún
  0 siblings, 1 reply; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-19 23:07 UTC (permalink / raw)
  To: Pavel Machek
  Cc: linuxarm, mauro.chehab, gregkh, linux-doc, linux-kernel, linux-leds

Em Wed, 19 May 2021 21:41:15 +0200
Pavel Machek <pavel@ucw.cz> escreveu:

> Hi!
> 
> > > 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.
> 
> > > 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.

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.

> 
> 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.

> 
> > 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.

> 
> > [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.

> 
> 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_frequency

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).

    /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

Thanks,
Mauro

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
  2021-05-19 18:30             ` Mauro Carvalho Chehab
@ 2021-05-20 11:00               ` Marek Behún
  2021-05-20 16:00                 ` Mauro Carvalho Chehab
  0 siblings, 1 reply; 44+ messages in thread
From: Marek Behún @ 2021-05-20 11:00 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: Marek Behún, linuxarm, mauro.chehab, Pavel Machek, gregkh,
	linux-kernel, linux-leds

On Wed, 19 May 2021 20:30:14 +0200
Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:

> Em Wed, 19 May 2021 17:55:03 +0200
> Marek Behún <kabel@kernel.org> escreveu:
> 
> > On Wed, 19 May 2021 16:24:13 +0200
> > Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:
> >   
> > > 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%.

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.

> 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

> > 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.
> >   
> > > 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.
> >   
> > > 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).    
> > 
> > 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.
> 
> > 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.  
> 
> 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.

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.

> 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.

> 	- strobe on every 10 seconds using up to 50% of brightness
> 	  when the system is hibernated;

Implement a LED private trigger.

> 	- use 100% of brigntness and don't blink when powered up.

Implement a LED private trigger.

> > 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).

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


> 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).

> 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.

> > 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.
> 
> > 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.

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

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
  2021-05-20 11:00               ` Marek Behún
@ 2021-05-20 16:00                 ` Mauro Carvalho Chehab
  2021-05-20 16:36                   ` Marek Behún
  0 siblings, 1 reply; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-20 16:00 UTC (permalink / raw)
  To: Marek Behún
  Cc: Marek Behún, linuxarm, mauro.chehab, Pavel Machek, gregkh,
	linux-kernel, linux-leds

Em Thu, 20 May 2021 13:00:14 +0200
Marek Behún <marek.behun@nic.cz> escreveu:

> On Wed, 19 May 2021 20:30:14 +0200
> Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:
> 
> > Em Wed, 19 May 2021 17:55:03 +0200
> > Marek Behún <kabel@kernel.org> escreveu:
> >   
> > > On Wed, 19 May 2021 16:24:13 +0200
> > > Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:
> > >     
> > > > 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%.  
> 
> Not a problem, there are other controller which also work this way,
> leds-turris-omnia for example. 

OK.

> 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.
> 
> > 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)

Ok.

How the blink pattern is specified? NUC supports 3 different
patterns (breathing, pulsing, strobing).

> - 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.

> > 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).  
> 
> 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

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.


> > 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).
> 
> > 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?

> 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.

> > > 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.
> >   
> > > 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.  
> 
> The problem is that this API is not yet available in upstream kernel. I
> will try to push it and return to you.

Ok.

> 
> In the meantime let's see what others think about your code.

Ok, thank you!

Thanks,
Mauro

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC
  2021-05-19 23:07       ` Mauro Carvalho Chehab
@ 2021-05-20 16:19         ` Marek Behún
  2021-05-20 19:16           ` Mauro Carvalho Chehab
  0 siblings, 1 reply; 44+ messages in thread
From: Marek Behún @ 2021-05-20 16:19 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: Pavel Machek, linuxarm, mauro.chehab, gregkh, linux-doc,
	linux-kernel, linux-leds

On Thu, 20 May 2021 01:07:20 +0200
Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:

> 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).

> 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.

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.

> ---
> 
> 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_frequency

I'd rather use one file for frequencies and one for intervals, and map
in to an array, but that is just my preference...

> 
> 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).
> 
>     /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

If 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.

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
  2021-05-20 16:00                 ` Mauro Carvalho Chehab
@ 2021-05-20 16:36                   ` Marek Behún
  2021-05-20 18:59                     ` Mauro Carvalho Chehab
  0 siblings, 1 reply; 44+ messages in thread
From: Marek Behún @ 2021-05-20 16:36 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: linuxarm, mauro.chehab, Pavel Machek, gregkh, linux-kernel, linux-leds

On Thu, 20 May 2021 18:00:28 +0200
Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:

> Em Thu, 20 May 2021 13:00:14 +0200
> Marek Behún <marek.behun@nic.cz> escreveu:
> 
> > On Wed, 19 May 2021 20:30:14 +0200
> > Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:
> >   
> > > Em Wed, 19 May 2021 17:55:03 +0200
> > > Marek Behún <kabel@kernel.org> escreveu:
> > >     
> > > > On Wed, 19 May 2021 16:24:13 +0200
> > > > Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:
> > > >       
> > > > > 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%.    
> > 
> > Not a problem, there are other controller which also work this way,
> > leds-turris-omnia for example.   
> 
> OK.
> 
> > 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.
> >   
> > > 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)  
> 
> Ok.
> 
> How the blink pattern is specified? NUC supports 3 different
> patterns (breathing, pulsing, strobing).
> 
> > - 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.
> 
> > > 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).    
> > 
> > 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  
> 
> 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?

>
> 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.

Have you looked into DSDT and SSDT tables?

> 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.

> 
> > > 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).
> >   
> > > 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.

> -
> 
> 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


^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
  2021-05-20 16:36                   ` Marek Behún
@ 2021-05-20 18:59                     ` Mauro Carvalho Chehab
  2021-05-20 20:07                       ` Marek Behún
  2021-05-26 14:47                       ` Pavel Machek
  0 siblings, 2 replies; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-20 18:59 UTC (permalink / raw)
  To: Marek Behún, linux-kernel, linux-leds
  Cc: linuxarm, mauro.chehab, Pavel Machek, gregkh

Em Thu, 20 May 2021 18:36:33 +0200
Marek Behún <kabel@kernel.org> escreveu:

> On Thu, 20 May 2021 18:00:28 +0200
> Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:
> 
> > Em Thu, 20 May 2021 13:00:14 +0200
> > Marek Behún <marek.behun@nic.cz> escreveu:
> >   
> > > On Wed, 19 May 2021 20:30:14 +0200
> > > Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:
> > >     
> > > > Em Wed, 19 May 2021 17:55:03 +0200
> > > > Marek Behún <kabel@kernel.org> escreveu:
> > > >       
> > > > > On Wed, 19 May 2021 16:24:13 +0200
> > > > > Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:
> > > > >         
> > > > > > 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%.      
> > > 
> > > Not a problem, there are other controller which also work this way,
> > > leds-turris-omnia for example.     
> > 
> > OK.
> >   
> > > 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.
> > >     
> > > > 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)    
> > 
> > Ok.
> > 
> > How the blink pattern is specified? NUC supports 3 different
> > patterns (breathing, pulsing, strobing).
> >   
> > > - 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.
> >   
> > > > 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).      
> > > 
> > > 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    
> > 
> > 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. 

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.

> 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.

> 
> >
> > 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.  
> 
> 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

> > 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.

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.

> > > > 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).
> > >     
> > > > 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.

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.

> > -
> > 
> > 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

Clear enough to me.

> 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

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC
  2021-05-20 16:19         ` Marek Behún
@ 2021-05-20 19:16           ` Mauro Carvalho Chehab
  2021-05-20 19:43             ` Marek Behún
  0 siblings, 1 reply; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-20 19:16 UTC (permalink / raw)
  To: Marek Behún
  Cc: Pavel Machek, linuxarm, mauro.chehab, gregkh, linux-doc,
	linux-kernel, linux-leds

Em Thu, 20 May 2021 18:19:19 +0200
Marek Behún <kabel@kernel.org> escreveu:

> On Thu, 20 May 2021 01:07:20 +0200
> Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:
> 
> > 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).

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?

> > 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.  
> 
> 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).

> 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.

> 
> > ---
> > 
> > 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_frequency  
> 
> 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

> 
> > 
> > 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).
> > 
> >     /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  
> 
> If the BIOS reports a LED being full RGB LED, you should register it
> via multicolor framework.

OK.

> 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

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC
  2021-05-20 19:16           ` Mauro Carvalho Chehab
@ 2021-05-20 19:43             ` Marek Behún
  2021-05-21  9:57               ` Mauro Carvalho Chehab
  0 siblings, 1 reply; 44+ messages in thread
From: Marek Behún @ 2021-05-20 19:43 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: Pavel Machek, linuxarm, mauro.chehab, gregkh, linux-doc,
	linux-kernel, linux-leds

On Thu, 20 May 2021 21:16:15 +0200
Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:

> 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?

Yes, but we should really try to map ethernet_activity to netdev and
disk_activity to a potential blkdev trigger :-) That's my opinion.

> 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?

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?
 
> > 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).
> 
> > 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.

No idea, but I would guess yes.

> > 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.
> 
> 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
> 
> > 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

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
  2021-05-20 18:59                     ` Mauro Carvalho Chehab
@ 2021-05-20 20:07                       ` Marek Behún
  2021-05-21  9:14                         ` Mauro Carvalho Chehab
  2021-05-26 14:47                       ` Pavel Machek
  1 sibling, 1 reply; 44+ messages in thread
From: Marek Behún @ 2021-05-20 20:07 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: linux-kernel, linux-leds, linuxarm, mauro.chehab, Pavel Machek, gregkh

On Thu, 20 May 2021 20:59:33 +0200
Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:

> > 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.   
> 
> 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.
>
> > 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.

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.

> > Have you looked into DSDT and SSDT tables?  
> 
> It doesn't help.

Pity :(

> > 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.

> 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.
> 
> > > > > 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).
> > > >       
> > > > > 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.  
> 
> 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.

> 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.

> > > -
> > > 
> > > 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  
> 
> Clear enough to me.
> 
> > 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.

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

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
  2021-05-20 20:07                       ` Marek Behún
@ 2021-05-21  9:14                         ` Mauro Carvalho Chehab
  2021-05-26 14:51                           ` Pavel Machek
  0 siblings, 1 reply; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-21  9:14 UTC (permalink / raw)
  To: Marek Behún
  Cc: linux-kernel, linux-leds, linuxarm, mauro.chehab, Pavel Machek, gregkh

Em Thu, 20 May 2021 22:07:03 +0200
Marek Behún <kabel@kernel.org> escreveu:

> On Thu, 20 May 2021 20:59:33 +0200
> Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:
> 
> > > 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.     
> > 
> > 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.
> >  
> > > 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.  
> 
> 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.

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.

> > > 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.

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.

> > > > 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?  
> 
> 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.

> > 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.

> 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

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC
  2021-05-20 19:43             ` Marek Behún
@ 2021-05-21  9:57               ` Mauro Carvalho Chehab
  0 siblings, 0 replies; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-21  9:57 UTC (permalink / raw)
  To: Marek Behún
  Cc: Pavel Machek, linuxarm, mauro.chehab, gregkh, linux-doc,
	linux-kernel, linux-leds

Em Thu, 20 May 2021 21:43:56 +0200
Marek Behún <kabel@kernel.org> escreveu:

> On Thu, 20 May 2021 21:16:15 +0200
> Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:
> 
> > 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?  
> 
> Yes, but we should really try to map ethernet_activity to netdev and
> disk_activity to a potential blkdev trigger :-) That's my opinion.
> 
> > 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?  
> 
> 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.

>  
> > > 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).
> >   
> > > 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.  
> 
> No idea, but I would guess yes.
> 
> > > 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.

> > 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
> >   
> > > 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.

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

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
  2021-05-20 18:59                     ` Mauro Carvalho Chehab
  2021-05-20 20:07                       ` Marek Behún
@ 2021-05-26 14:47                       ` Pavel Machek
  2021-05-28 11:24                         ` Mauro Carvalho Chehab
  1 sibling, 1 reply; 44+ messages in thread
From: Pavel Machek @ 2021-05-26 14:47 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: Marek Behún, linux-kernel, linux-leds, linuxarm,
	mauro.chehab, gregkh

[-- Attachment #1: Type: text/plain, Size: 990 bytes --]

Hi!

> > > 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. 
> 
> 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
-- 
http://www.livejournal.com/~pavelmachek

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
  2021-05-21  9:14                         ` Mauro Carvalho Chehab
@ 2021-05-26 14:51                           ` Pavel Machek
  2021-05-28 11:33                             ` Mauro Carvalho Chehab
  0 siblings, 1 reply; 44+ messages in thread
From: Pavel Machek @ 2021-05-26 14:51 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: Marek Behún, linux-kernel, linux-leds, linuxarm,
	mauro.chehab, gregkh

[-- Attachment #1: Type: text/plain, Size: 555 bytes --]

Hi!

> > 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
-- 
http://www.livejournal.com/~pavelmachek

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
  2021-05-26 14:47                       ` Pavel Machek
@ 2021-05-28 11:24                         ` Mauro Carvalho Chehab
  0 siblings, 0 replies; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-28 11:24 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Marek Behún, linux-kernel, linux-leds, linuxarm,
	mauro.chehab, gregkh

Em Wed, 26 May 2021 16:47:51 +0200
Pavel Machek <pavel@ucw.cz> escreveu:

> Hi!
> 
> > > > 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.   
> > 
> > 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

^ permalink raw reply	[flat|nested] 44+ messages in thread

* Re: [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator
  2021-05-26 14:51                           ` Pavel Machek
@ 2021-05-28 11:33                             ` Mauro Carvalho Chehab
  0 siblings, 0 replies; 44+ messages in thread
From: Mauro Carvalho Chehab @ 2021-05-28 11:33 UTC (permalink / raw)
  To: Pavel Machek
  Cc: Marek Behún, linux-kernel, linux-leds, linuxarm,
	mauro.chehab, gregkh

Em Wed, 26 May 2021 16:51:12 +0200
Pavel Machek <pavel@ucw.cz> escreveu:

> Hi!
> 
> > > 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.

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

^ permalink raw reply	[flat|nested] 44+ messages in thread

end of thread, other threads:[~2021-05-28 11:33 UTC | newest]

Thread overview: 44+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-05-18 15:08 [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC Mauro Carvalho Chehab
2021-05-18 15:08 ` [PATCH v2 01/17] docs: describe the API used to set NUC LEDs Mauro Carvalho Chehab
2021-05-18 15:08 ` [PATCH v2 02/17] leds: add support for NUC WMI LEDs Mauro Carvalho Chehab
2021-05-18 15:08 ` [PATCH v2 03/17] leds: leds-nuc: detect WMI API detection Mauro Carvalho Chehab
2021-05-18 15:08 ` [PATCH v2 04/17] leds: leds-nuc: add support for changing S0 brightness Mauro Carvalho Chehab
2021-05-18 15:08 ` [PATCH v2 05/17] leds: leds-nuc: add all types of brightness Mauro Carvalho Chehab
2021-05-18 15:08 ` [PATCH v2 06/17] leds: leds-nuc: allow changing the LED colors Mauro Carvalho Chehab
2021-05-18 15:08 ` [PATCH v2 07/17] leds: leds-nuc: add support for WMI API version 1.0 Mauro Carvalho Chehab
2021-05-18 15:08 ` [PATCH v2 08/17] leds: leds-nuc: add basic support for NUC6 WMI Mauro Carvalho Chehab
2021-05-18 15:08 ` [PATCH v2 09/17] leds: leds-nuc: add brightness and color for NUC6 API Mauro Carvalho Chehab
2021-05-18 15:08 ` [PATCH v2 10/17] leds: leds-nuc: Add support to blink behavior for NUC8/10 Mauro Carvalho Chehab
2021-05-19  7:58   ` Marek Behun
2021-05-19 10:09     ` Mauro Carvalho Chehab
2021-05-18 15:09 ` [PATCH v2 11/17] leds: leds-nuc: get rid of an unused variable Mauro Carvalho Chehab
2021-05-18 15:09 ` [PATCH v2 12/17] leds: leds-nuc: implement blink control for NUC6 Mauro Carvalho Chehab
2021-05-18 15:09 ` [PATCH v2 13/17] leds: leds-nuc: better detect NUC6/NUC7 devices Mauro Carvalho Chehab
2021-05-18 15:09 ` [PATCH v2 14/17] leds: leds-nuc: add support for HDD activity default Mauro Carvalho Chehab
2021-05-18 15:09 ` [PATCH v2 15/17] leds: leds-nuc: fix software blink behavior logic Mauro Carvalho Chehab
2021-05-18 15:09 ` [PATCH v2 16/17] leds: leds-nuc: add support for changing the ethernet type indicator Mauro Carvalho Chehab
2021-05-19  8:02   ` Marek Behún
2021-05-19 10:18     ` Mauro Carvalho Chehab
2021-05-19 12:11       ` Marek Behún
2021-05-19 14:24         ` Mauro Carvalho Chehab
2021-05-19 15:55           ` Marek Behún
2021-05-19 18:30             ` Mauro Carvalho Chehab
2021-05-20 11:00               ` Marek Behún
2021-05-20 16:00                 ` Mauro Carvalho Chehab
2021-05-20 16:36                   ` Marek Behún
2021-05-20 18:59                     ` Mauro Carvalho Chehab
2021-05-20 20:07                       ` Marek Behún
2021-05-21  9:14                         ` Mauro Carvalho Chehab
2021-05-26 14:51                           ` Pavel Machek
2021-05-28 11:33                             ` Mauro Carvalho Chehab
2021-05-26 14:47                       ` Pavel Machek
2021-05-28 11:24                         ` Mauro Carvalho Chehab
2021-05-18 15:09 ` [PATCH v2 17/17] leds: leds-nuc: add support for changing the power limit scheme Mauro Carvalho Chehab
2021-05-19 11:11 ` [PATCH v2 00/17] Adding support for controlling the leds found on Intel NUC Pavel Machek
2021-05-19 12:15   ` Mauro Carvalho Chehab
2021-05-19 19:41     ` Pavel Machek
2021-05-19 23:07       ` Mauro Carvalho Chehab
2021-05-20 16:19         ` Marek Behún
2021-05-20 19:16           ` Mauro Carvalho Chehab
2021-05-20 19:43             ` Marek Behún
2021-05-21  9:57               ` Mauro Carvalho Chehab

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).